Node.js 分布式追踪实战:OpenTelemetry 自动埋点与自定义 Span

为 Node.js 应用接入 OpenTelemetry 分布式追踪:NodeSDK 初始化、自动埋点覆盖 Express/HTTP/MySQL/Redis、自定义 Span 补充业务语义、Collector 管道配置,最终导入观测云 APM 实现全链路分析。附完整代码。

最佳实践
Node.js 分布式追踪实战:OpenTelemetry 自动埋点与自定义 Span封面

Node.js 分布式追踪的接入路径是"NodeSDK + 自动埋点包"——@opentelemetry/auto-instrumentations-node 一个包覆盖 Express、HTTP、MySQL、PostgreSQL、Redis、MongoDB 等几十个常见库,启动时加 --require 即可生效,业务代码零改动。

核心要点速览

  • 自动埋点通过模块劫持实现,必须在应用代码加载前启动;
  • 自定义 Span 用于标注关键业务步骤,与自动 Span 自动串联;
  • 链路经 Collector 汇聚转发,应用与后端解耦;
  • 端点指向观测云 DataKit,APM 直接出服务拓扑与火焰图。

第一步:初始化 NodeSDK

新建 tracing.js(必须在应用启动前加载):

// tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { Resource } = require('@opentelemetry/resources');

const sdk = new NodeSDK({
  resource: new Resource({ 'service.name': 'api-gateway' }),
  traceExporter: new OTLPTraceExporter({
    url: 'http://datakit-host:4318/v1/traces',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();

process.on('SIGTERM', () => sdk.shutdown());

启动方式:node --require ./tracing.js app.jsservice.name 决定 APM 中的服务名,务必规范。

第二步:配置 Collector 管道

应用先把链路发给 Collector,再由 Collector 批量转发:

receivers:
  otlp:
    protocols:
      http: {endpoint: 0.0.0.0:4318}
processors:
  batch: {timeout: 5s}
exporters:
  otlp:
    endpoint: "http://datakit:4318"
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]

第三步:定制自动埋点

自动埋点可按库精细控制——比如关掉嘈杂的 DNS 查询 Span、给 HTTP Span 附加自定义属性:

instrumentations: [getNodeAutoInstrumentations({
  '@opentelemetry/instrumentation-dns': { enabled: false },
  '@opentelemetry/instrumentation-http': {
    requestHook: (span, request) => {
      span.setAttribute('app.tenant', request.headers['x-tenant-id'] || 'unknown');
    },
    ignoreIncomingRequestHook: (req) => req.url === '/health',  // 健康检查不生成 Span
  },
})],

第四步:补充自定义 Span

自动埋点覆盖框架层,业务关键步骤手动补 Span:

const opentelemetry = require('@opentelemetry/api');
const tracer = opentelemetry.trace.getTracer('checkout-service');

async function checkout(cartId) {
  return tracer.startActiveSpan('checkout.process', async (span) => {
    try {
      span.setAttribute('cart.id', cartId);
      span.setAttribute('cart.item_count', items.length);
      await validateInventory();
      await chargePayment();
      span.setStatus({ code: opentelemetry.SpanStatusCode.OK });
    } catch (err) {
      span.recordException(err);
      span.setStatus({ code: opentelemetry.SpanStatusCode.ERROR, message: err.message });
      throw err;
    } finally {
      span.end();
    }
  });
}

recordException 会把异常堆栈记进 Span,后端可直接检索含错误的链路。

Node.js 异步上下文注意事项

AsyncLocalStorage 让 async/await、Promise 链中的上下文自动延续,但两个场景需留意:回调风格的老代码(用 context.with 显式包裹)、跨进程的异步任务(消息队列需手动传播上下文,见《上下文传播》)。

观测云落地:链路进 APM 闭环

导出端点指向观测云 DataKit 后,观测云 APM 自动呈现:服务拓扑图展示 API 网关与各下游的调用关系,链路列表支持按耗时/状态过滤,火焰图逐层下钻到具体 Span,ERROR 链路自动标红。配合日志 trace_id 注入,从报错日志一键跳到完整链路,排障动线全打通。

常见问题(FAQ)

Q:自动埋点会影响性能吗? 常规开销在 5% 以内;如果应用本身高频创建大量短 Span(如高频 Redis 轮询),可按需关闭个别 instrumentation。

Q:Next.js / Serverless 环境适用吗? Vercel 等平台内置 OTel 支持;传统 Serverless 需用平台的 OTel 层(Layer)或自行初始化,注意冷启动期 exporter 初始化的延迟。

Q:为什么我的 Span 没有父级、链路是散的? 多半是自定义 Span 创建时上下文丢失——确认在 startActiveSpan 回调内执行业务逻辑,或在 Promise 链中检查 context 传递。

Q:本地开发怎么调试? 把 exporter 换成 ConsoleSpanExporter 直接打印,或指向本地 Jaeger,验证通过后再切观测云。

系列阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台