☰
使用 @envelop/newrelic 为 GraphQL Yoga 应用接入 New Relic 监控与分布式追踪
2026/9/26 3:09:22 网站建设 项目流程
  • 后端
  • API设计

【免费下载链接】graphql-yoga

🧘 Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance & great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.

项目地址:https://gitcode.com/gh_mirrors/gr/graphql-yoga
点击查看免费下载

本文以 @envelop/newrelic 插件文档 为核心,介绍如何在基于 Envelop / GraphQL Yoga 的 GraphQL 应用中接入 New Relic Node.js Agent,通过分布式追踪监控操作(operation)与解析器(resolver)的性能和错误。读完本文,你将掌握插件的安装配置、全部选项的语义与默认值、基于正则表达式的变量/参数白名单黑名单过滤,以及 newrelic.js 与环境变量两种 Agent 配置方式,并能从源码与测试层面理解其工作原理。

说明:@envelop/newrelic属于本仓库packages/envelop/plugins/newrelic目录,是 Envelop 插件体系的一员,可直接与 @envelop/core 组合使用;GraphQL Yoga 同样基于 Envelop 构建,因此该插件也适用于 Yoga 服务。

插件能做什么

@envelop/newrelic为你的 GraphQL 应用提供 New Relic 上报能力,核心价值在于:

  • 分布式追踪(Distributed Tracing):将 GraphQL 操作接入 New Relic 的跨服务追踪链路,定位性能瓶颈与错误根因;
  • 操作级监控:以 GraphQL 操作(operation)为单位记录事务(transaction),可携带操作名、操作类型、请求文档、变量与执行结果;
  • Resolver 级监控:把每个解析器的调用记录为 segment,展示根字段(root-field)与子字段(sub-field)的逐级耗时;
  • 错误追踪:将执行结果中的GraphQLError上报给 New Relic Agent,并支持自定义跳过规则。

插件依赖 New Relic 官方 Node.js Agent(newrelicnpm 包),自身只负责把 GraphQL 的语义信息桥接到 Agent 的 API 上,最终的上报、采样、展示均由 Agent 与 New Relic 平台完成。

快速开始

按官方文档,接入分三步:安装依赖 → 配置 Agent → 注册插件。

安装

yarn add newrelic @envelop/newrelic

从 package.json 可以看到本插件的版本要求:

  • Node.js>=18.0.0;
  • 作为peerDependency的@envelop/core;
  • graphql支持^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0;
  • newrelic支持>=7 <12(当前开发环境使用newrelic@11.0.0)。

基本用法

在创建 Envelop 实例时,将插件加入 plugins 数组:

import { execute, parse, specifiedRules, subscribe, validate } from 'graphql' import { envelop, useEngine } from '@envelop/core' import { useNewRelic } from '@envelop/newrelic' const getEnveloped = envelop({ plugins: [ useEngine({ parse, validate, specifiedRules, execute, subscribe }), // ... other plugins ... useNewRelic({ includeOperationDocument: true, // 默认 false。为 true 时,把定义操作与片段的 GraphQL 文档作为属性上报 includeExecuteVariables: false, // 默认 false。为 true 时,把操作变量及值全部上报 includeRawResult: false, // 默认 false。为 true 时,把执行结果上报 trackResolvers: true, // 默认 false。为 true 时,把 resolver 记录为 segment 以监控性能 includeResolverArgs: false, // 默认 false。为 true 时,把传给 resolver 的参数及值全部上报 rootFieldsNaming: true, // 默认 false。为 true 时,把操作根字段名追加到事务名中 skipError: error => { return true // 允许你决定某个错误是否上报给 NewRelic。默认情况下自定义的 EnvelopError 会被跳过 }, extractOperationName: context => context.request.body.customOperationName // 从 context 中提取自定义操作名,用于事务名与属性 }) ] })

所有选项在源码 src/index.ts 中都有明确的默认值:

const DEFAULT_OPTIONS: UseNewRelicOptions = { includeOperationDocument: false, includeExecuteVariables: false, includeRawResult: false, trackResolvers: false, includeResolverArgs: false, rootFieldsNaming: false, skipError: () => false, };

其中skipError的默认行为需要留意:文档与源码注释指出,插件默认会跳过EnvelopError(Envelop 自定义错误类型)的上报,避免把业务校验错误当作系统故障;而对普通Error则按默认规则上报(源码中skipError默认实现为恒返回false,即不跳过)。

重要提示:事务(transaction)与 segment/span 的计时可能受其他插件影响。为了获得更准确的追踪数据,官方建议把 New Relic 插件放在插件列表的最后。

选项详解与底层实现

结合 src/index.ts,我们逐项说明各选项的语义及其在源码中的落点。

事务命名与操作级属性

插件在onExecute阶段执行核心逻辑(src/index.ts#L141-L241):

  1. 通过getOperationAST(args.document, args.operationName)解析出根操作,拿不到则直接放弃记录;
  2. 确定操作名,优先级为:extractOperationName(context)→args.operationName→rootOperation.name?.value→ 匿名占位符<anonymous>(源码中的AttributeName.ANONYMOUS_OPERATION);
  3. 如果开启了rootFieldsNaming,会从 selectionSet 中收集所有根字段名(只统计Kind.FIELD节点);
  4. 事务名被设置为operationType + delimiter + operationName (+ delimiter + rootFields.join('&')),例如query/Greetings/hello(分隔符来自 Agent 的transactionNameState.delimiter,测试见 tests/newrelic.spec.ts);
  5. 通过getSpanContext()向 span 写入自定义属性。

插件写入的属性名集中在AttributeName枚举中(src/index.ts#L8-L21):

枚举值属性名含义
COMPONENT_NAMEEnvelop_NewRelic_Plugin组件标识,同时用于注册Supportability/ExternalModules/Envelop_NewRelic_Plugin指标
EXECUTION_OPERATION_NAMEgraphql.execute.operationName操作名
EXECUTION_OPERATION_TYPEgraphql.execute.operationType操作类型(query / mutation / subscription)
EXECUTION_OPERATION_DOCUMENTgraphql.execute.documentGraphQL 文档字符串(开启includeOperationDocument时写入)
EXECUTION_VARIABLESgraphql.execute.variables操作变量 JSON(开启includeExecuteVariables时写入)
EXECUTION_RESULTgraphql.execute.result执行结果 JSON(开启includeRawResult且结果含data时写入)
RESOLVER_FIELD_PATHgraphql.resolver.fieldPathresolver 字段路径,如country/name
RESOLVER_TYPE_NAMEgraphql.resolver.typeName所属类型名,如Query
RESOLVER_RESULT_TYPEgraphql.resolver.resultType返回类型,如Country、String!
RESOLVER_RESULTgraphql.resolver.resultresolver 返回结果 JSON(开启includeRawResult时写入)
RESOLVER_ARGSgraphql.resolver.argsresolver 参数 JSON(开启includeResolverArgs时写入)
  • includeOperationDocument:把定义操作与片段的完整 GraphQL 文档写入graphql.execute.document。文档字符串通过@envelop/core的getDocumentString(args.document, print)获取。
  • includeExecuteVariables:把操作变量 JSON 写入graphql.execute.variables;变量取自args.variableValues。
  • includeRawResult:操作成功后把执行结果写入graphql.execute.result;同时每个被追踪的 resolver 会把返回值写入graphql.resolver.result(仅当includeRawResult开启)。
  • extractOperationName:接收 Envelop 的DefaultContext,返回自定义操作名,同时用于事务名与graphql.execute.operationName属性。该能力在 4.0.0 版本由operationNameProperty改为函数式 API(见 CHANGELOG.md),好处是你可以读取 context 中的嵌套属性或来自其他 Envelop 插件的上下文扩展。

错误处理与 skipError

在onExecuteDone中,插件对执行结果逐条检查错误(src/index.ts#L204-L239):

if (singularResult.errors && singularResult.errors.length > 0) { const agent = instrumentationApi.agent; const transaction = instrumentationApi.tracer.getTransaction(); if (transaction) { for (const error of singularResult.errors) { if (options.skipError?.(error)) continue; agent.errors.add(transaction, JSON.stringify(error)); } } }
  • skipError接收每个GraphQLError,返回true表示跳过该错误的上报;
  • 测试用例验证了这一点:当skipError: e => e.message === 'Ignore me!'时,抛错 resolver 所在事务的hasError为false(tests/newrelic.spec.ts#L177-L198)。

流式结果支持

对于订阅(subscription)等异步可迭代结果,插件通过isAsyncIterable判断,并使用onNext逐条上报、onEnd关闭 operation segment(src/index.ts#L226-L235)。这一能力在 3.2.0 版本加入("support async iterable results"),保证订阅类操作的错误与数据同样被记录。

Resolver 级追踪的工作原理

开启trackResolvers: true后,插件会在onPluginInit阶段动态注入基于@envelop/on-resolve的钩子(src/index.ts#L81-L139):

  • useOnResolve在onSchemaChange阶段遍历 schema 中所有对象类型的字段,把每个字段的 resolver 包上一层拦截逻辑(packages/envelop/plugins/on-resolve/src/index.ts),默认跳过 introspection 查询;
  • 在 resolver 调用前,插件通过instrumentationApi.getActiveSegment()拿到当前活动 segment,为其创建名为resolver/<路径>的子 segment(例如resolver/country、resolver/country/name);
  • 字段路径由flattenPath递归拼接info.path得到,数字类型索引(如列表项下标)会被跳过(src/index.ts#L245-L257);
  • 为 segment 写入graphql.resolver.fieldPath、graphql.resolver.typeName、graphql.resolver.resultType;
  • resolver 返回后(包括异步 Promise 场景),若开启includeRawResult则写入返回结果 JSON,并结束 segment(回调形式({ result }) => { ...; resolverSegment.end(); });
  • 若当前没有活动事务或活动 segment(例如 Agent 未初始化),插件会通过 logger 以trace级别记录原因并跳过,不会影响业务执行。

另外,插件在初始化时会先检查 Agent 是否可用(rawOptions?.shim || newRelic?.shim以及instrumentationApi?.agent)。若 Agent 不可用(未正确安装newrelic、配置缺失或被禁用),会打印警告并返回空插件,避免应用崩溃(src/index.ts#L65-L72)。注册成功后还会上报一个Supportability/ExternalModules/Envelop_NewRelic_Plugin指标,测试中对此有断言(tests/newrelic.spec.ts#L79-L83)。

高级用法:正则过滤变量与参数

除了布尔值,includeExecuteVariables和includeResolverArgs还接受RegExp,用于对追踪内容做白名单/黑名单过滤。这在防止泄露用户数据(如 PII)的同时保留调试所需的字段非常有用。

useNewRelic({ includeExecuteVariables: /client|application/i, // 白名单:只追踪名称含 "client" 或 "application" 的变量(如 clientName、applicationId、xApplicationId) trackResolvers: true, // 追踪 resolver,因为同时想追踪 resolver 参数 includeResolverArgs: /^(?!name|email|password).*/i, // 黑名单:追踪所有名称不等于 name、email、password 的参数 }),

实现上,插件在初始化时用instanceof RegExp判断是否为正则(options.isExecuteVariablesRegex/options.isResolverArgsRegex),随后通过filterPropertiesByRegex遍历对象键、用pattern.test(property)逐键筛选(src/index.ts#L259-L267)。

测试用例提供了直观的验证:

  • includeExecuteVariables: true+ 变量{ name: 'Laurin' }→ 上报{"name":"Laurin"}(tests/newrelic.spec.ts#L86-L114);
  • includeExecuteVariables: /verb/+ 变量{ verb: 'Hi', name: 'Dotan' }→ 只上报{"verb":"Hi"}(tests/newrelic.spec.ts#L115-L150)。

性能提醒:过滤变量和参数的方式是循环遍历,因此会产生 O(n) 的开销,其中n为操作变量个数(追踪执行变量时)或传给 resolver 的参数个数(追踪 resolver 参数时)。在生产环境请权衡过滤粒度与开销,尤其是高 QPS 的服务。

Agent 配置:newrelic.js 与环境变量

插件本身只负责桥接,真正的 Agent 配置需要按 New Relic 官方文档完成。官方推荐两种配置方式:

  1. newrelic.js 文件:放在应用根目录。New Relic 官方仓库提供了该文件的基础示例(对应node-newrelic的newrelic.js模板);
  2. 环境变量:与 newrelic.js 中的配置项一一对应,只需全部大写、以NEW_RELIC_前缀开头,并在应用启动前确保变量已就绪。

两个必填项:

描述newrelic.js环境变量
应用名app_name: ['MyAppName']NEW_RELIC_APP_NAME=MyAppName
许可证密钥license_key: '40HexadecimalCharacters'NEW_RELIC_LICENSE_KEY=40HexadecimalCharacters

常用配置项:

描述newrelic.js环境变量
开启分布式追踪distributed_tracing: { enabled: true }NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=true
日志级别logging: { level: 'info' }NEW_RELIC_LOG_LEVEL=info
捕获所有请求头allow_all_headers: trueNEW_RELIC_ALLOW_ALL_HEADERS=true
开启错误收集error_collector: { enabled: true }NEW_RELIC_ERROR_COLLECTOR_ENABLED=true

更多可配置项可参考 New Relic Node.js Agent 的官方配置文档及其lib/config/default.js(该文件列出了 newrelic.js 中可包含的全部配置变量,均可转换为对应的环境变量)。注意:分布式追踪是本文场景(跨服务定位 GraphQL 请求根因)的基础,建议显式开启。

监控效果一览

以下截图来自插件 README,展示的是所有插件选项均为true时的 New Relic 界面效果。

错误追踪与操作/Resolver 视图:

成功操作追踪——操作、根字段与子字段 resolver 视图:

从截图可以看到:操作名为myCustomQuery的查询在 New Relic 中被记录为一次分布式追踪,右侧 Attributes 面板展示graphql.execute.operationName、graphql.execute.operationType、请求文档、变量与结果;span 列表中resolver/country、resolver/language等顶级 resolver 与resolver/country/name等子字段 resolver 被逐级展开,错误场景下还会标注具体的GraphQLError错误类与出错 resolver(如resolver/country)及其入参({"code":"GB"})、字段路径、结果类型等信息,帮助快速定位究竟是哪个字段、哪个入参导致耗时异常或失败。

小结

@envelop/newrelic以很小的接入成本为 GraphQL 服务补齐了 APM 能力:操作级事务命名与属性、resolver 级 segment、错误上报、基于正则的敏感数据过滤一应俱全,且对query/mutation/subscription三类操作均做了处理。其实现完全建立在 Envelop 的onExecute/onExecuteDone钩子与@envelop/on-resolve的 resolver 拦截机制之上,不侵入业务代码;需要留意的是 Agent 未初始化时插件会静默降级(打印警告、不记录),以及按官方建议将插件置于 plugins 数组末尾以获得更准确的计时。

  • 后端
  • API设计

【免费下载链接】graphql-yoga

🧘 Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance & great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.

项目地址:https://gitcode.com/gh_mirrors/gr/graphql-yoga
点击查看免费下载
上一篇:OmniGibson完整指南:照片级渲染与物理仿真兼备的Embodied AI仿真平台
下一篇:暗影精灵性能解锁终极指南:OmenSuperHub风扇曲线、功耗与灯效控制完整教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询