
こんにちは。よっしーです(^^)
背景
この連載では、Claude Codeの公式ドキュメントを1ページずつ読み解いていきます。公式ドキュメントは情報が網羅されている分、「結局どの機能を、どんな場面で使えばいいのか」は自分で考える必要があり、読むのに意外と時間がかかります。そこで、私が実務で使うために読み込んだ内容を「使う場面→実例コード」の順に整理して残していくことにしました。専門家の解説というより、一次情報を読んだ記録です。推測や動作を確認していない部分には、その都度そう書きます。
公式ドキュメント:https://code.claude.com/docs/ja/agent-sdk/observability
1. 一言でいうと
Agent SDK で動かすエージェントが「どのツールを呼んだか」「各モデルリクエストにどれだけ時間がかかったか」「トークンをどれだけ使ったか」「どこで失敗したか」を、OpenTelemetry のトレース・メトリクス・ログイベントとして外部の監視基盤に送る方法です。送り出すのは SDK ではなく、SDK が起動する Claude Code CLI のサブプロセスで、設定はすべて環境変数で渡します。既定ではプロンプトやファイルの中身は送られず、送るかどうかは変数ごとに自分で選びます。
第94回のホスティングで「OTEL の環境変数を設定すれば、すべての query() がスパン・メトリクス・ログをコレクターに送る」と1節だけ触れた内容の本体です。第95回で見た「プロキシですべての通信を記録する」がエージェントの外側からの記録だとすると、今回はエージェントの内側で何が起きたかの記録です。
2. どういう場面で役立つか
シーン1:本番で止まったり遅くなったりしたエージェントの原因を追う
あるセッションだけ応答が遅い、途中で止まった、という場合に、どのモデルリクエストに時間がかかったのか、どのツールで権限の確認待ちになっていたのかを、スパンの木として見られます。第94回は「テレメトリがなければ、どのツールが動いたか、どれだけかかったか、どこで止まったかが分からない」と書いていました。
シーン2:アプリケーション全体のトレースの中にエージェントを組み込む
Web アプリの1リクエストの中でエージェントを呼んでいる場合、アプリのトレースの子としてエージェントの処理を表示できます。「API が遅い」の原因がエージェントなのか、その前後のデータベースなのかを、1本のトレースで切り分けられます。
シーン3:エンドユーザーごとの監査証跡を作る
1つのサービスで多数の利用者にエージェントを提供している場合、「どの利用者の依頼で、どのツールが実行されたか」を記録して、SIEM(セキュリティ情報・イベント管理)の基盤に送れます。
不要・向かないケース
- トークン数や費用を知りたいだけ:外部の基盤に送らなくても、SDK のレスポンスストリームから直接読めます(第92回のコスト追跡)。原文も冒頭でそちらを案内しています。
- 監視基盤(OTLP を受け付けるバックエンドやコレクター)を持っていない:このページの内容は、送り先があって初めて意味を持ちます。手元で中身を確かめたいだけでも、後述のとおり
consoleエクスポーターは使えないので、ローカルのコレクターを立てる必要があります。 - エージェントが扱う中身(プロンプト、ファイル、コマンド出力)を外に出せない環境で、中身まで見たい:中身の記録はすべてオプトインで、出してよいと決まっていない限り有効にしないよう原文は求めています(3-6)。
3. コードと仕組みの解説
原文の fenced コードブロックは8本で、すべて Python と TypeScript の対訳です(エクスポートの有効化、送信間隔の短縮、サービス名とリソース属性、エンドユーザー ID の付与)。どれも内容が違うので全数を引用します。コードは原文から機械的に抜き出したもので、入れ子に由来する先頭インデントを外した以外は変えていません。2組目以降のコードにある # ... 前の例のエクスポーター設定 ... のような行は、原文が「最初の例の設定をここに入れる」という意味で置いている注記です。このページでは原文が Python を先に並べているので、その順で載せます。
3-0. バージョン要件
| 機能 | 必要なバージョン |
|---|---|
CLAUDE_CODE_OTEL_DIAG_STDERR=1 でエクスポーターのエラーを表示する | Claude Code v2.1.179 以降 |
アクティブなスパンの外で出たイベントレコードにも trace_id・span_id が付く | v2.1.212 以降(それより前は付かない) |
OTEL_LOG_TOOL_CONTENT=1、CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH によるインラインの切り詰め上限 | Claude Code v2.1.214 以降 |
OTEL_LOG_TOOL_CONTENT=1 で MCP ツール・WebFetch・WebSearch の結果も含める | Claude Code v2.1.283 以降 |
SDK は Claude Code CLI を同梱して動かす(第94回)ので、ここでの「Claude Code のバージョン」は、使っている SDK に同梱された CLI のバージョンのことです。
3-1. テレメトリはどこから出るか
Agent SDK は Claude Code CLI を子プロセスとして起動し、ローカルのパイプでやりとりします(第94回のサブプロセスモデル)。OpenTelemetry の計測は CLI に組み込まれています。 CLI は、モデルリクエストとツール実行のそれぞれをスパンで囲んで記録し、トークンと費用のカウンターをメトリクスとして出し、プロンプトやツール結果の構造化されたログイベントを出します。
SDK 自身はテレメトリを作りません。 設定を CLI のプロセスに渡すだけで、CLI がコレクターに直接送ります。そのため、送信先への通信は CLI のプロセスから出ていきます。第95回のように出口をプロキシに絞っている構成では、コレクターへの通信も許可しておく必要があります(筆者の指摘)。
設定を渡す2つの場所
設定は環境変数で渡します。子プロセスは既定でアプリケーションの環境を引き継ぐので、置き場所は2つあります。
| 置き場所 | 方法 | 向いている場面 |
|---|---|---|
| プロセスの環境 | アプリケーションを起動する前に、シェル・コンテナ・オーケストレーターで変数を設定する。どの query() もコードを変えずに拾う | 本番の推奨 |
| 呼び出しごとのオプション | Python は ClaudeAgentOptions.env、TypeScript は options.env | 同じプロセスの中で、エージェントごとにテレメトリの設定を変えたいとき |
2行目には言語差があります。Python の env は引き継いだ環境の上に重ねられ、TypeScript の env は引き継いだ環境を丸ごと置き換えます。 TypeScript では渡すオブジェクトに ...process.env を含める必要があります(第79回・第94回と同じ注意)。
3つのシグナル
CLI は互いに独立した3つのシグナルを出します。それぞれ有効化のスイッチとエクスポーターが別なので、必要なものだけをオンにできます。
| シグナル | 中身 | 有効にする変数 |
|---|---|---|
| メトリクス | トークン、費用、セッション、コードの行数、ツールの判断のカウンター | OTEL_METRICS_EXPORTER |
| ログイベント | プロンプト、API リクエスト、API エラー、ツール結果ごとの構造化レコード | OTEL_LOGS_EXPORTER |
| トレース | やりとり、モデルリクエスト、ツール呼び出し、フック(ベータ)ごとのスパン | OTEL_TRACES_EXPORTER と CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 |
メトリクス名・イベント名・属性の全一覧は、本体側の Monitoring リファレンスにあります。Agent SDK は同じ CLI を動かすので、出てくるデータも同じです。
3-2. エクスポートを有効にする
テレメトリは、CLAUDE_CODE_ENABLE_TELEMETRY=1 を設定し、少なくとも1つのエクスポーターを選ぶまでオフです。最もよくある設定は、3つのシグナルすべてを OTLP の HTTP でコレクターに送るものです。次の例は変数を辞書(オブジェクト)にまとめて env で渡し、エージェントに1つのタスクをさせます。ループがレスポンスを読んでいる間に、CLI が collector.example.com のコレクターにスパン・メトリクス・イベントを送ります。
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
OTEL_ENV = {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
# トレースに必須(ベータ版)。メトリクスとログイベントは不要です。
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
# シグナルごとにエクスポーターを選択します。SDK には otlp を使用します。以下の注記を参照してください。
"OTEL_TRACES_EXPORTER": "otlp",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
# 標準 OTLP トランスポート設定。
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.example.com:4318",
"OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer your-token",
}
async def main():
options = ClaudeAgentOptions(env=OTEL_ENV)
async for message in query(
prompt="List the files in this directory", options=options
):
print(message)
asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";
const otelEnv = {
CLAUDE_CODE_ENABLE_TELEMETRY: "1",
// トレースに必須(ベータ版)。メトリクスとログイベントは不要です。
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
// シグナルごとにエクスポーターを選択します。SDK には otlp を使用します。以下の注記を参照してください。
OTEL_TRACES_EXPORTER: "otlp",
OTEL_METRICS_EXPORTER: "otlp",
OTEL_LOGS_EXPORTER: "otlp",
// 標準 OTLP トランスポート設定。
OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
OTEL_EXPORTER_OTLP_ENDPOINT: "http://collector.example.com:4318",
OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Bearer your-token",
};
for await (const message of query({
prompt: "List the files in this directory",
// TypeScript では env は継承された環境を置き換えるため、最初に
// process.env をスプレッドして PATH、ANTHROPIC_API_KEY、その他の変数を保持します。
options: { env: { ...process.env, ...otelEnv } },
})) {
console.log(message);
}
コードの各行の役割を整理します。
| 変数 | 役割 |
|---|---|
CLAUDE_CODE_ENABLE_TELEMETRY | テレメトリ全体のスイッチ |
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA | トレースに必須(ベータ)。メトリクスとログイベントだけなら不要 |
OTEL_TRACES_EXPORTER・OTEL_METRICS_EXPORTER・OTEL_LOGS_EXPORTER | シグナルごとのエクスポーター。SDK では otlp を使う |
OTEL_EXPORTER_OTLP_PROTOCOL・OTEL_EXPORTER_OTLP_ENDPOINT・OTEL_EXPORTER_OTLP_HEADERS | 標準の OTLP の送信設定(プロトコル、送信先、認証ヘッダー) |
TypeScript では、コメントにあるとおり { ...process.env, ...otelEnv } と、先に process.env を展開してから OTEL の変数を重ねています。これを忘れると PATH や ANTHROPIC_API_KEY が消えます。Python の例は env=OTEL_ENV だけで、引き継いだ環境に重ねる動作に任せています。
子プロセスは既定でアプリケーションの環境を引き継ぐので、同じ変数を Dockerfile、Kubernetes のマニフェスト、シェルのプロファイルで設定し、options.env を丸ごと省いても同じ結果になります。3-1 の表のとおり、本番ではこちらが推奨です。
筆者の指摘:例の OTEL_EXPORTER_OTLP_HEADERS には Authorization=Bearer your-token というコレクターの認証トークンが入ります。この変数は CLI のプロセスの環境に入るので、第95回の「認証情報をエージェントの境界の外に置く」という原則からすると、エージェントと同じ環境に秘密を置いていることになります。原文は Bash ツールのコマンドが環境変数をどこまで引き継ぐかを書いていませんが、コマンドから読める可能性を前提にするなら、認証の要らないローカルのコレクター(同じホストや隣のコンテナ)に送り、そこから先の認証付きの転送をコレクター側で行う構成のほうが、第95回の考え方に合います。
届いているか確かめる:エラーは既定で出ない
エクスポートが動いているかは、タスクが終わった後に、コレクターのログで受け取ったスパン・メトリクス・ログイベントを見て確かめます。
注意すべきは、CLI はエクスポートの失敗を既定で黙って捨てることです。送信先に届かなくても、データが拒否されても、エージェントは普通に動き続け、CLI はテレメトリを捨て、アプリケーションにエラーを見せません。エクスポーターのエラーを見るには、エクスポーターの変数と一緒に CLAUDE_CODE_OTEL_DIAG_STDERR=1 を設定し、SDK の stderr コールバック(Python)または stderr オプション(TypeScript)で診断を読みます(v2.1.179 以降)。
第93回の mirror_error と同じく「失敗しても止まらない」設計です。止まらないのは本業を守るためですが、監査のためにテレメトリを使うなら、届いていないことに気づく手段を別に持つ必要があります(筆者の指摘)。
console エクスポーターは使わない
原文は注記で、SDK 経由で動かすときは、エクスポーターの値に console を設定しないよう求めています。console エクスポーターはテレメトリを標準出力に書きますが、標準出力は SDK がメッセージのやりとりに使っている経路だからです。手元で中身を確かめたいときは、OTEL_EXPORTER_OTLP_ENDPOINT をローカルの OpenTelemetry Collector に向けます。
第94回で見たとおり、SDK と CLI は stdio でやりとりしています。そこに関係のない文字列が混ざれば、メッセージの読み取りが壊れうる、ということです(筆者の解釈。原文は「設定しないでください」とだけ書き、壊れ方は書いていません)。
3-3. 短い呼び出しでもデータを取りこぼさない
CLI はテレメトリをまとめて(バッチで)一定間隔で送ります。プロセスがきれいに終わるときは残りを送り出そうとしますが、その送り出しにはタイムアウトがあるので、コレクターの応答が遅いとスパンが落ちることがあります。CLI がシャットダウンする前にプロセスが強制終了されると、バッファにあったものはすべて失われます。送信間隔を短くすると、この2つの取りこぼしの窓が両方とも小さくなります。
既定の間隔は、メトリクスが60秒ごと、トレースとログが5秒ごとです。次の例は3つとも1000ミリ秒にして、短いタスクの実行中にデータがコレクターに届くようにします。
OTEL_ENV = {
# ... 前の例のエクスポーター設定 ...
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
"OTEL_LOGS_EXPORT_INTERVAL": "1000",
"OTEL_TRACES_EXPORT_INTERVAL": "1000",
}
const otelEnv = {
// ... 前の例のエクスポーター設定 ...
OTEL_METRIC_EXPORT_INTERVAL: "1000",
OTEL_LOGS_EXPORT_INTERVAL: "1000",
OTEL_TRACES_EXPORT_INTERVAL: "1000",
};
既定のメトリクスの60秒は、数秒で終わるタスクから見ると長すぎます。第94回のエフェメラルなパターン(タスクが終わればコンテナを捨てる)では、コンテナの停止がそのまま「シャットダウン前の強制終了」になりうるので、間隔を短くしておく意味が大きいはずです(筆者の対応づけ)。
3-4. トレースを読む
トレースは、エージェントの実行を最も細かく見られるシグナルです。CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 を設定すると、エージェントループの各ステップがスパンになります。
| スパン | 何を囲むか | 補足 |
|---|---|---|
claude_code.interaction | エージェントループの1ターン(プロンプトを受けてから応答を作るまで) | 他のスパンの親 |
claude_code.llm_request | Claude API への各呼び出し | モデル名、レイテンシ、トークン数が属性に入る |
claude_code.tool | 各ツール呼び出し | 子スパンに、権限の確認待ち(claude_code.tool.blocked_on_user)と実行そのもの(claude_code.tool.execution) |
claude_code.hook | 各フックの実行 | 詳細なベータのトレース(ENABLE_BETA_TRACING_DETAILED=1 と BETA_TRACING_ENDPOINT)が必要。この組はログとトレースの送り先も変える |
claude_code.tool の下で「確認待ち」と「実行」が分かれているのは実務上便利です。ツールが遅いのか、承認が返ってこないのか(第81回の canUseTool が待っているのか)を区別できます(筆者の指摘)。
llm_request・tool・hook のスパンは、それを囲む claude_code.interaction の子です。エージェントが Agent ツールでサブエージェントを起動すると、サブエージェントの llm_request と tool のスパンは、親エージェントの claude_code.tool スパンの下に入れ子になります。委任の連鎖全体が1本のトレースとして見える、ということです。第89回のサブエージェントの動きを、外から1本の木として追えます。
セッションでまとめる
スパンは既定で session.id 属性を持ちます。同じセッション(第87回)に対して query() を何度も呼ぶ場合、バックエンドで session.id で絞り込めば、1本のタイムラインとして並べられます。OTEL_METRICS_INCLUDE_SESSION_ID を偽の値にすると、この属性は付きません。
原文は注記で、トレースはベータで、スパン名と属性はリリースごとに変わる可能性があると書いています。ダッシュボードやアラートをスパン名に依存させる場合は、SDK を更新するたびに確認が要ります(筆者の指摘)。
3-5. アプリケーションのトレースにつなげる
自動で親子関係がつく
SDK は、W3C トレースコンテキストを CLI のサブプロセスに自動で伝えます。アプリケーションで OpenTelemetry のスパンがアクティブな状態で query() を呼ぶと、SDK は TRACEPARENT と TRACESTATE を子プロセスの環境に差し込み、CLI がそれを読むので、claude_code.interaction スパンがアプリケーションのスパンの子になります。エージェントの実行が、つながりのない別のルートではなく、アプリケーションのトレースの中に現れます。
実行中に出る OTLP のイベントログレコードも同じトレースコンテキストを持ちます。TRACEPARENT が設定されていれば、各レコードの trace_id と span_id がアプリケーションのトレースと一致するので、イベントをバックエンドでスパンに結びつけられます。v2.1.212 より前は、アクティブなスパンの外で出たイベントレコードには trace_id も span_id も付きませんでした。
Bash のコマンドにも伝わる
トレースコンテキストの伝播が有効なとき、CLI は実行するすべての Bash と PowerShell のコマンドに TRACEPARENT を渡します。Bash ツールで起動したコマンド自身が OpenTelemetry のスパンを出す場合、それらはそのコマンドを囲む claude_code.tool.execution スパンの下に入ります。エージェントが走らせたテストやビルドのスクリプトが計測されていれば、その中身までエージェントのトレースの続きとして見える、ということです。
自動の注入を止める・CLI では効かない
| 状況 | 動き |
|---|---|
options.env で TRACEPARENT を明示的に設定する | 自動の注入は飛ばされる。特定の親コンテキストに固定したいとき |
| 対話型の CLI セッション | 受け取った TRACEPARENT を完全に無視する |
Agent SDK と claude -p | TRACEPARENT を尊重する |
表は筆者のまとめです。第75回のヘッドレス(claude -p)をパイプラインから呼んでいる場合も、呼び出し側が TRACEPARENT を環境に入れれば、同じようにつながることになります。
3-6. タグを付けて見分ける
既定では、CLI は service.name を claude-code として報告します。複数のエージェントを動かす場合や、同じコレクターに他のサービスも送っている場合は、サービス名を上書きし、リソース属性を足して、バックエンドでエージェントごとに絞り込めるようにします。次の例は、サービス名を変え、デプロイのメタデータ(バージョンと環境)を付けます。これらの値は、そのエージェントが出すすべてのスパン・メトリクス・イベントに、OpenTelemetry のリソース属性として付きます。
options = ClaudeAgentOptions(
env={
# ... Enable telemetry export の例からのエクスポーター設定 ...
"OTEL_SERVICE_NAME": "support-triage-agent",
"OTEL_RESOURCE_ATTRIBUTES": "service.version=1.4.0,deployment.environment=production",
},
)
const options = {
env: {
...process.env,
// ... Enable telemetry export の例からのエクスポーター設定 ...
OTEL_SERVICE_NAME: "support-triage-agent",
OTEL_RESOURCE_ATTRIBUTES:
"service.version=1.4.0,deployment.environment=production",
},
};
OTEL_SERVICE_NAME がサービス名、OTEL_RESOURCE_ATTRIBUTES が「キー=値」をコンマでつないだ属性の列です。ここでも TypeScript だけ ...process.env を展開しています。
3-7. エンドユーザーごとに記録する
既定の ID 属性は「サービス」を指す
CLI は、Anthropic を呼ぶのに使っている認証情報をもとに、すべてのイベントに ID 属性を付けます。1つのデプロイで多くの利用者にサービスを提供するアプリケーションでは、この属性が示すのはサービスの認証情報で、エージェントが誰の代わりに動いたかではありません。
ツール呼び出しや MCP の活動をアプリケーションの利用者に結びつけるには、query() を呼ぶたびに、利用者の ID をリソース属性として差し込みます。OTEL_RESOURCE_ATTRIBUTES はコンマ・スペース・等号を予約しているので、値を埋め込む前にパーセントエンコードします。次の例は、Web フレームワークの request オブジェクトにユーザー ID とテナント ID が入っている前提で、1つのリクエストから出るすべてのスパンとイベントに、依頼したユーザーとテナントを付けます。
from urllib.parse import quote
options = ClaudeAgentOptions(
env={
# ... エクスポーター設定 ...
"OTEL_RESOURCE_ATTRIBUTES": f"enduser.id={quote(request.user_id)},tenant.id={quote(request.tenant_id)}",
},
)
const options = {
env: {
...process.env,
// ... エクスポーター設定 ...
OTEL_RESOURCE_ATTRIBUTES: `enduser.id=${encodeURIComponent(request.userId)},tenant.id=${encodeURIComponent(request.tenantId)}`,
},
};
エンコードには Python が urllib.parse の quote、TypeScript が encodeURIComponent を使っています。フィールド名も言語の書き方に合わせて、Python が request.user_id・request.tenant_id、TypeScript が request.userId・request.tenantId です。
エンコードを省くと、たとえばテナント名にコンマが含まれていた場合に、属性の区切りと誤解されて別の属性ができてしまいます。利用者が自分で決められる値を埋め込むなら、これは記録の改ざんにもつながりうるので、エンコードは必須と考えるべきです(筆者の指摘)。
筆者の指摘:OTEL_RESOURCE_ATTRIBUTES は1つの変数なので、呼び出しごとの env でこれを設定すると、プロセスの環境で設定していた値(たとえば 3-6 の service.version や deployment.environment)は、その呼び出しでは変数ごと上書きされると読めます。Python の env は「環境に重ねる」動作ですが、重ねる単位は変数で、変数の中の属性ではないからです。デプロイの属性と利用者の属性を両方付けたいなら、1つの文字列にまとめて渡す必要があるでしょう。
利用者ごとの監査証跡になるイベント
利用者の ID が付くと、次のイベント(claude_code. の接頭辞付きのログレコードとして出る)が、利用者ごとの監査証跡になり、SIEM の基盤に転送できます。
tool_decisiontool_resultmcp_server_connectionpermission_mode_changed
セキュリティに関わるイベントの全一覧と各イベントの属性は、Monitoring リファレンスの「セキュリティイベントの監査」にあります。
3-8. 送るデータの中身を管理する
テレメトリは既定で構造化された情報だけです。所要時間、モデル名、ツール名はすべてのスパンに記録されます。トークン数は、元の API リクエストが使用量を返したときに記録されるので、失敗したり中断したりしたリクエストのスパンには無いことがあります。そして、エージェントが読み書きする中身は、既定では記録されません。
中身を加えるのは、次のオプトインの変数です。
| 変数 | 加わるもの | 条件 |
|---|---|---|
OTEL_LOG_USER_PROMPTS=1 | claude_code.user_prompt イベントと claude_code.interaction スパンにプロンプトの本文 | — |
OTEL_LOG_TOOL_DETAILS=1 | claude_code.tool_result イベントにツールの入力引数(ファイルパス、シェルコマンド、検索パターン)。コストとトークンのメトリクスに、実際のエージェント・スキル・プラグイン・MCP サーバーの名前 | — |
OTEL_LOG_TOOL_CONTENT=1 | claude_code.tool の tool.output スパンイベントと、ファイルの内容、Bash の出力、MCP ツール・WebFetch・WebSearch が返したもの。既定で 60 KB で切り詰め、CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH で変更可 | v2.1.214 以降(MCP・WebFetch・WebSearch の結果は v2.1.283 以降)。トレースが有効であること。スパン属性は独自のゲートのもとでツールの内容を保持する |
OTEL_LOG_RAW_API_BODIES | Anthropic Messages API のリクエストとレスポンスの JSON 全体を、claude_code.api_request_body・claude_code.api_response_body のログイベントとして。1 なら既定で 60 KB に切り詰めたものをイベント内に、file:<dir> なら切り詰めないものをディスクに置き、イベントには body_ref のパス | CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH はインラインの切り詰め上限で、v2.1.214 以降 |
4行目には重要な但し書きがあります。ボディには会話の履歴全体が入ります(拡張思考の内容は編集されます)。そのため、これを有効にすることは、上の3つの変数が明かすすべての中身に同意したことを意味します。 上の3つをオフにしていても、4つ目をオンにすれば、プロンプトもツールの入出力も、会話履歴の一部としてそのまま出ていく、ということです。
原文の指示は明確で、エージェントが扱うデータを監視基盤に保存することが承認されていない限り、これらは設定しないままにする、です。属性と編集の動作の全一覧は、Monitoring リファレンスの「セキュリティとプライバシー」にあります。
第95回の観点を重ねると、OTEL_LOG_TOOL_CONTENT で Bash の出力やファイルの中身を送る設定は、エージェントが読んだ .env の中身や認証情報も監視基盤に複製しうる、ということです。第95回で「マウントする前に除く」とされたファイルが残っている環境では、監視基盤が新しい漏えい先になりえます(筆者の指摘)。また file:<dir> の場合は、切り詰めていない会話全体がディスクに残るので、その置き場所の保護と削除も設計に入れる必要があります(筆者の指摘)。
4. まとめ
- テレメトリを出すのは SDK ではなく CLI のサブプロセス。設定は環境変数で渡す。本番はプロセスの環境に置くのが推奨。呼び出しごとの
envは TypeScript だと置き換えなので...process.envが要る。 - シグナルはメトリクス・ログイベント・トレースの3つで、それぞれ別に有効化する。トレースには
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1も要る。全体のスイッチはCLAUDE_CODE_ENABLE_TELEMETRY=1。 - エクスポートの失敗は既定で黙って捨てられる。確かめるには
CLAUDE_CODE_OTEL_DIAG_STDERR=1(v2.1.179 以降)とstderrで診断を読む。 consoleエクスポーターは使わない(標準出力は SDK のメッセージの経路)。手元ではローカルのコレクターに送る。- 既定の送信間隔はメトリクス60秒、トレースとログ5秒。短いタスクや強制終了で取りこぼすので、間隔を短くする。
- スパンは
interactionの下にllm_request・tool(確認待ちと実行に分かれる)・hook。サブエージェントのスパンは親のtoolの下に入れ子になり、委任の連鎖が1本のトレースになる。session.idでセッションをまとめる。トレースはベータで名前が変わりうる。 - アクティブなスパンがあると
TRACEPARENTが自動で注入され、アプリのトレースにつながる。Bash・PowerShell のコマンドにも渡る。明示的に設定すれば自動注入は止まる。対話型 CLI は無視し、SDK とclaude -pだけが尊重する。 OTEL_SERVICE_NAMEとOTEL_RESOURCE_ATTRIBUTESで見分ける。既定の ID 属性はサービスの認証情報を指すので、利用者 ID は呼び出しごとにパーセントエンコードしてリソース属性に入れる。するとtool_decisionなどが利用者ごとの監査証跡になる。- 中身(プロンプト、ツールの入力・出力、API のボディ)は既定で送られない。
OTEL_LOG_RAW_API_BODIESは会話全体を含み、他の3つへの同意を意味する。承認がない限り設定しない。
次回予告
次回は 「Agent SDK のトラブルシューティング」(agent-sdk/troubleshooting) を取り上げる予定です。
今回、エクスポートの失敗は黙って捨てられ、診断を見るには stderr を読む必要があると分かりました。第94回でも、手元では動くのにデプロイ先で「CLI が見つからない」「起動しない」「途中で終了する」という失敗の型を見て、詳しい対処はトラブルシューティングのページに回されていました。次回は、そうした失敗のときにアプリケーションがどんなエラーを受け取り、どこを確かめて直せばよいのかを見ていきます。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント