关于 otel-node
面向 Node.js 应用的 OpenTelemetry 技能,涵盖遥测初始化、追踪采集及导出故障排查,尤其关注 ESM 打包问题。适合需要接入追踪或诊断采集链路的开发者。
主要功能
- 01NodeSDK 初始化与 OTLP 导出
- 02HTTP、Express、PostgreSQL 和 Redis 自动埋点
- 03手动创建 Span 与上下文传播
- 04诊断并绕过 ESM 打包埋点故障
- 05验证 Collector 采集链路
适用场景
- 01为 Node.js 服务添加追踪
- 02排查 esbuild ESM 输出中没有 Span 的问题
- 03为 Hono 编写显式中间件埋点
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mizchi/ skills --skill otel-node安装后在 Claude Code 中输入/otel-node确认已加载。
附带文件README.md
详细介绍
OpenTelemetry — Node.js
SDK 初始化
// otel.ts — import before everything else
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { Resource } from "@opentelemetry/resources";
import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
const exporter = new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + "/v1/traces",
});
export const sdk = new NodeSDK({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: process.env.SERVICE_NAME ?? "my-service",
}),
spanProcessor: new BatchSpanProcessor(exporter),
});
sdk.start();
process.on("SIGTERM", () => sdk.shutdown());
在应用入口之前加载:node --import ./otel.js server.js 或 tsx --import ./otel.ts server.ts。
自动插桩包
只添加所需的包——每个包都会修补特定模块:
pnpm add @opentelemetry/instrumentation-http # node:http / node:https
pnpm add @opentelemetry/instrumentation-express # Express routing spans
pnpm add @opentelemetry/instrumentation-pg # postgres queries
pnpm add @opentelemetry/instrumentation-ioredis # Redis commands
在 SDK 初始化时注册:
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
export const sdk = new NodeSDK({
instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()],
// ...
});
Hono:没有官方插桩包。请使用手动中间件(见下文)。
esbuild ESM — 自动插桩静默失效
症状
应用正常启动,SDK 初始化日志也会出现,但没有 span 到达收集器。没有错误。以下情况会出现此问题:
- 输出格式为
--format=esm - 使用
@opentelemetry/instrumentation-*自动插桩 - 打包工具为 esbuild(Vite / SWC 也一样,根本原因相同)
根本原因
@opentelemetry/instrumentation-* 使用 require-in-the-middle 钩住 require() 并对目标模块(node:http 等)进行猴子补丁。在 esbuild ESM bundle 中,import 会在打包时静态解析——运行时不会调用 require() → 钩子不会触发 → 不会应用补丁 → 没有 span。不会抛出错误(静默失效)。
修复方案 A — 手动创建 span(推荐用于 Hono/esbuild)
编写一个显式创建 span 的中间件:
import { context, propagation, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
const tracer = trace.getTracer("app");
app.use(async (c, next) => {
const ctx = propagation.extract(context.active(), c.req.raw.headers);
const route = new URL(c.req.url).pathname;
await tracer.startActiveSpan(
`${c.req.method} ${route}`,
{ kind: SpanKind.SERVER, attributes: { "http.method": c.req.method, "http.route": route } },
ctx,
async (span) => {
try {
await next();
span.setAttribute("http.status_code", c.res.status);
span.setStatus({ code: SpanStatusCode.OK });
} catch (e: any) {
span.recordException(e);
span.setStatus({ code: SpanStatusCode.ERROR, message: e?.message });
throw e;
} finally {
span.end();
}
}
);
});
通过元数据将上下文传播到发出的 gRPC 调用:
const callUnary = (method, req) => {
const metadata = new grpc.Metadata();
propagation.inject(context.active(), metadata, {
set: (carrier, key, value) => (carrier as grpc.Metadata).set(key, value),
});
return new Promise((resolve, reject) =>
client[method](req, metadata, (err, res) => (err ? reject(err) : resolve(res)))
);
};
修复方案 B — 切换到 CJS 输出
将 esbuild 改为 --format=cjs。require() 钩子会正常触发。如果没有仅支持 ESM 的依赖,就可以使用。Hono 支持 CJS。
修复方案 C — 不进行打包
使用 tsx 或 node --loader ts-node/esm + --import @opentelemetry/auto-instrumentations-node/register。跳过对 Node 进程的打包。会增大容器镜像体积;不建议用于生产环境。
验证管道
临时向 OTel Collector 添加一个调试导出器:
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlphttp/tempo, debug]
收到 span 时,收集器日志会显示 Trace ID: ... Name: GET /api/users。没有输出 = 收集器之前的环节存在问题。
相关内容
opentelemetry— 信号设计、span 命名、采样、W3C 传播cloudflare-workers-otel-utels— Cloudflare Workers 遥测(无 Node 运行时,采用 fetch 边界方案)- esbuild、Vite 和 SWC 都存在相同的
require-in-the-middle失效模式 - 截至 2026-05,
instrumentation-*包不支持静态 import 修补