otel-node について
Node.js アプリの OpenTelemetry 計装とトレース出力の問題解決を扱うスキルです。特に ESM バンドルでの自動計装の制約を診断し、開発者が収集パイプラインを確認する方法を示します。
主な機能
- 01NodeSDK の初期化と OTLP エクスポート
- 02HTTP、Express、PostgreSQL、Redis の自動計装
- 03手動スパンとコンテキスト伝搬
- 04ESM バンドル計装の問題診断と回避策
- 05Collector パイプラインの確認
活用シーン
- 01Node.js サービスへのトレース追加
- 02esbuild の ESM 出力でスパンが届かない問題の調査
- 03Hono への明示的なミドルウェア計装
インストール
コマンドをコピーしてターミナルで実行します。
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 初期化ログも表示されますが、コレクターにスパンが届きません。エラーは発生しません。次の場合に発生します:
- 出力形式が
--format=esm @opentelemetry/instrumentation-*による自動計装を使用している- バンドラーが esbuild(Vite/SWC も同じ根本原因です)
根本原因
@opentelemetry/instrumentation-* は require-in-the-middle を使用して require() をフックし、対象モジュール(node:http など)にモンキーパッチを適用します。esbuild の ESM バンドルでは、import はバンドル時に静的に解決されます。実行時に require() が呼び出されないため、フックが発火せず、パッチも適用されず、スパンも生成されません。エラーは発生しません(ひそかな失敗)。
修正方法 A — 手動スパン(Hono/esbuild では推奨)
スパンを明示的に作成するミドルウェアを記述します:
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]
スパンが届くと、コレクターのログに Trace ID: ... Name: GET /api/users と表示されます。出力がない場合は、コレクターに到達する前のどこかで問題が発生しています。
関連項目
opentelemetry— シグナル設計、スパン名、サンプリング、W3C 伝搬cloudflare-workers-otel-utels— Cloudflare Workers のテレメトリー(Node ランタイムなし、fetch 境界方式)- esbuild、Vite、SWC はすべて、同じ
require-in-the-middleの障害モードを共有しています instrumentation-*パッケージは、2026-05 時点で静的インポートへのパッチ適用に対応していません