opentelemetry について
OpenTelemetry の基本実践を扱うスキルで、シグナル選択、スパン設計、コンテキスト伝搬、サンプリング、OTLP エクスポートを解説します。サービス間のテレメトリー実装やトラブルシューティングを行う開発者向けです。
主な機能
- 01トレース、メトリクス、ログの選択
- 02スパン名、属性、イベント、エラー処理
- 03W3C コンテキスト伝搬
- 04サンプリング戦略の指針
- 05OTLP エクスポーター設定と注意点
活用シーン
- 01アプリケーションのシグナル選定
- 02分散リクエストのスパン設計
- 03コンテキスト断絶やエクスポート欠落の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mizchi/ skills --skill opentelemetryインストール後、Claude Code で/opentelemetryを入力して読み込まれたか確認します。
同梱ファイルREADME.md
詳細
OpenTelemetry — コアパターン
シグナルの選択
コードを書く前に、適切なシグナルを選びます。
| シグナル | 用途 | コスト |
|---|---|---|
| Traces | リクエストのライフサイクル、レイテンシの帰属、分散システムにおける因果関係 | 高(リクエストごと) |
| Metrics | 集計済みのカウント、レート、ヒストグラム — ダッシュボードやアラート | 低(事前に集計済み) |
| Logs | コンテキストを伴う個別のイベント — エラー、監査、デバッグ | 中 |
目安:メトリクスは「どれくらいの頻度か/どれくらい速いか」、ログは「何が起きたか」、トレースは「なぜか」に答えます。メトリクスで十分な場合に、トレースを使わないでください。
スパンの設計
命名規則
<verb> <noun> → "fetch user", "send email"
<provider>.<operation> → "db.query", "http.get", "cache.set"
可変データ(ID、値)をスパン名に含めないでください。代わりに属性を使います。スパン名はトレースインデックスのカーディナリティキーです。
属性
span.setAttributes({
"db.system": "sqlite",
"db.operation": "select",
"db.sql.table": "users",
"user.id": userId, // high-cardinality: OK as attribute, not in name
});
既知の属性名(http.*、db.*、rpc.* など)には、OpenTelemetry Semantic Conventions に従ってください。バックエンドや APM はこれらを基準に処理します。
イベントと子スパン
- イベント:現在の操作内の瞬間的な時点(
span.addEvent("cache_miss")) - 子スパン:独自の所要時間を持ち、レイテンシが重要で、トレースのウォーターフォール表示に役立ちます
ステータスと例外
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
span.recordException(err); // adds exception.type / exception.message / exception.stacktrace
エラー時には両方を呼び出してください。recordException だけではステータスは ERROR に変わらず、UI 上ではスパンが成功したように表示されます。
span.end() の保証
// ✓ callback form guarantees end()
tracer.startActiveSpan("operation", (span) => {
try {
return doWork();
} catch (e) {
span.recordException(e as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw e;
} finally {
span.end();
}
});
終了していないスパンはプロセッサーキューに残り、エクスポートされない可能性があります。
コンテキスト伝播
W3C TraceContext(traceparent / tracestate)が標準です。送信リクエストではインジェクトし、受信リクエストでは抽出します。
import { propagation, context } from "@opentelemetry/api";
// Outgoing HTTP
const carrier: Record<string, string> = {};
propagation.inject(context.active(), carrier);
fetch(url, { headers: carrier });
// Incoming (server handler)
const ctx = propagation.extract(context.active(), request.headers);
tracer.startActiveSpan("handle request", { context: ctx }, (span) => {
// span is now a child of the upstream trace
});
最もよくあるバグ:受信したコンテキストを抽出せずにスパンを作成すると、トレースのウォーターフォールが、つながりのないルートスパンに分断されます。ルートのサーバースパンを開始する前に、必ず抽出してください。
サンプリング
| 戦略 | 使用する場面 |
|---|---|
AlwaysOn | 開発環境/トラフィックの少ないステージング環境 |
TraceIdRatioBased(0.1) | トラフィックの多い本番環境 — 新しいトレースの 10% をサンプリングします |
ParentBased(root: TraceIdRatioBased) | 推奨されるデフォルト — 上流の判断を尊重し、新しいルートを指定した比率でサンプリングします |
| テールサンプリング(OTel Collector) | ヘッドサンプリングの判断にかかわらず、エラーを 100% 取得する必要がある場合 |
ParentBased を使うと、上流がトレースをサンプリングしたのに下流が破棄するという失敗モード(ウォーターフォールの分断)を防げます。
OTLP エクスポーターの設定
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
const exporter = new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + "/v1/traces",
headers: { Authorization: `Bearer ${process.env.OTEL_API_KEY}` },
});
provider.addSpanProcessor(
new BatchSpanProcessor(exporter, {
maxQueueSize: 512,
scheduledDelayMillis: 2000,
exportTimeoutMillis: 10_000,
})
);
本番環境では、非同期でオーバーヘッドの少ない BatchSpanProcessor を使ってください。SimpleSpanProcessor はイベントループをブロックするため、ローカルでのデバッグにのみ使用します。
よくある落とし穴
provider.register()が呼び出されていない:SDK は設定されていますが、何もエクスポートされません。インストルメンテーションが実行される前に呼び出してください。- ESM と自動インストルメンテーション(Node.js):ESM の静的インポートでは
require-in-the-middleのフックが実行されません。回避策についてはotel-nodeを参照してください。 - Cloudflare Workers:Node.js ランタイムがなく、fetch 境界でのインストルメンテーションが必要です。
cloudflare-workers-otel-utelsを参照してください。 - スパン名に動的データが含まれている:トレースインデックスのカーディナリティが急増します。属性に移してください。
- 送信呼び出しで W3C 伝播が行われていない:分散トレースが分断され、下流のスパンが孤立したルートとして表示されます。