Claude CodeにOpenTelemetryを正しく実装させる──スパン管理・コンテキスト伝播・エクスポーター設定をCLAUDE.mdで制御する

# Claude CodeにOpenTelemetryを正しく実装させる──スパン管理・コンテキスト伝播・エクスポーター設定をCLAUDE.mdで制御する

## はじめに

Claude CodeはOTel計装コードを生成できるが、廃止済みエクスポーターを使ったり`span.end()`を例外時に呼ばなかったりする。動作するように見えてトレースが欠落する問題だ。CLAUDE.mdでこれらのパターンを制御すれば最初から正しい計装コードが生成される。

---

## Claude CodeがOTelで生成しやすい誤りを正す

### 非推奨エクスポーターの使用(`exporter-jaeger`)

`@opentelemetry/exporter-jaeger`は2024年3月にサポート終了。Claude CodeはこのパッケージのNPMダウンロードが今でも多いため生成しやすい。

```typescript
// ❌ 2024年3月でサポート終了
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
const sdk = new NodeSDK({ traceExporter: new JaegerExporter({ endpoint: 'http://localhost:14268/api/traces' }) });
```

```typescript
// ✅ OTLPエクスポーター(環境変数推奨: OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318)
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
const sdk = new NodeSDK({ traceExporter: new OTLPTraceExporter() });
```

Jaeger v1.35以降はOTLPをネイティブサポート(ポート4317/4318)しているためJaeger専用エクスポーターは不要だ。

### スパンの非対称終了(`span.end()`忘れ・`setStatus`省略)

Claude CodeはHappy Pathは正しく書けるが例外発生時のスパン終了が不完全になりやすい。`recordException()`はSpan Eventを記録するだけでStatusをERRORに変えない(公式仕様)。

```typescript
// ❌ finallyなし(例外時にspan.end()が呼ばれない → スパンがリーク)
tracer.startActiveSpan('fetchUser', async (span) => {
const user = await fetchUser(userId);
span.end();
return user;
});
```

```typescript
// ✅ try-catch-finallyパターン
import { SpanStatusCode } from '@opentelemetry/api';
tracer.startActiveSpan('processOrder', async (span) => {
try {
const result = await processOrder(orderId);
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
span.recordException(error as Error);
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
throw error;
} finally { span.end(); }
});
```

### コンテキスト伝播の省略(サービス間の`propagation.inject()`欠落)

`@opentelemetry/instrumentation-http`の自動計装は`node:http/https`のみ有効。`fetch` APIでは手動注入が必要で省略するとトレースが断絶する。

```typescript
// ❌ traceparentヘッダーが欠落(トレースが断絶)
const response = await fetch(url, { headers: { 'Content-Type': 'application/json' } });
```

```typescript
// ✅ 送信側: propagation.inject でコンテキストを注入
import { propagation, context } from '@opentelemetry/api';
const carrier: Record = {};
propagation.inject(context.active(), carrier);
await fetch(url, { headers: { ...carrier } });

// 受信側: extract + context.with でコンテキスト復元
const ctx = propagation.extract(context.active(), req.headers);
context.with(ctx, () => tracer.startActiveSpan('handler', async (span) => {
try { /* 処理 */ } finally { span.end(); }
}));
```

---

## CLAUDE.mdで制御する完全テンプレート

```markdown
## OpenTelemetry実装ルール

### パッケージ
- @opentelemetry/exporter-jaeger 禁止(2024年3月EOL)
- HTTP: exporter-trace-otlp-http / gRPC: exporter-trace-otlp-grpc
- エンドポイントは OTEL_EXPORTER_OTLP_ENDPOINT 環境変数で設定

### スパン管理
- startActiveSpan 内で必ず try-catch-finally を使う
- finally: span.end() / catch: recordException(error) + setStatus(ERROR) の両方必須
- recordException() だけではStatusはERRORにならない(公式仕様)

### コンテキスト伝播
- fetch/axios では propagation.inject(context.active(), carrier) で注入
- 受信側: propagation.extract(context.active(), headers) + context.with() でコンテキスト復元
```

---

## PostToolUseフックでTypeScript型エラーを自動検出する

`.ts`ファイル編集後にTypeScript型チェックを自動実行する。OTelコードの型定義ミスをその場で検出できる。

```json
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path // empty'); [[ \"$FILE\" == *.ts ]] && npx tsc --noEmit 2>&1 | head -20",
"timeout": 30
}]
}]
}
}
```

型エラーがあればClaude Codeのコンテキストに出力が渡されその場で自己修正する。

---

## EM視点──OTel実装の標準化

CLAUDE.mdで標準化すれば誰が使っても同じパターンのOTelコードが生成される。既存コードは「`exporter-jaeger`が存在しないか」「全スパンが`try-catch-finally`になっているか」「`recordException()`の後に`setStatus(ERROR)`があるか」「`fetch`で`propagation.inject()`があるか」の4点をgrepして確認するとよい。

---

## まとめ

Claude CodeがOTelで踏む地雷は`exporter-jaeger`・`span.end()`忘れ・`propagation.inject()`省略の3点だ。CLAUDE.mdに本記事のテンプレートを追記して今すぐリポジトリにコミットしてほしい。PostToolUseフックを合わせて設定すれば、型定義ミスもその場で自動修正される。

コメント

タイトルとURLをコピーしました