Agent SDKをOpenTelemetryで可観測化する実装ガイド
Agent SDKのテレメトリはCLIが計装する仕組みで、SDKは環境変数経由で設定を橋渡しするだけです。有効化からトレース連結、機微データ制御までを実装コードで説明します。
Agent SDKのテレメトリはどこから来るか
Agent SDKはClaude Code CLIを子プロセスとして起動し、ローカルのパイプで通信します。テレメトリを作っているのはSDK自身ではなく、この子プロセスのCLIです。CLIはモデルリクエストとツール実行の周りにスパンを記録し、トークンとコストのカウンターをメトリクスとして出し、プロンプトとツール結果を構造化ログイベントとして出します。SDKはこの設定を子プロセスへ橋渡しするだけで、独自にテレメトリを生成しません。
橋渡しの経路は環境変数です。子プロセスは既定でアプリケーションの環境変数を継承するため、設定できる場所は2つに分かれます。プロセス環境は、シェルやコンテナ、オーケストレーターであらかじめ変数を設定しておく方法で、query()の呼び出しごとにコード変更なしで反映されます。本番運用ではこちらが基本です。呼び出しごとのoptionsは、同一プロセス内で複数のエージェントに別々のテレメトリ設定を持たせたいときに使います。Pythonのenvは継承した環境の上にマージされますが、TypeScriptのoptions.envは継承環境を完全に置き換えるため、process.envを明示的に展開して渡す必要があります。
CLIは3種類のシグナルを独立に出力します。メトリクスはトークン・コスト・セッション数などのカウンター、ログイベントはプロンプトごとのAPIリクエストやツール結果の構造化レコード、トレースは各インタラクション・モデルリクエスト・ツール呼び出し・フックのスパンです。トレースだけはベータ扱いで、有効化に専用のフラグが要ります。
前提条件と有効化の最小構成
テレメトリは既定で無効です。CLAUDE_CODE_ENABLE_TELEMETRY=1を設定し、シグナルごとに少なくとも1つのエクスポーターを選ぶまで何も送信されません。もっとも一般的な構成は、3シグナルすべてをOTLP HTTPでコレクターへ送る形です。
import { query } from "@anthropic-ai/claude-agent-sdk";
const otelEnv = {
CLAUDE_CODE_ENABLE_TELEMETRY: "1",
// トレースはベータのため必須。メトリクスとログイベントには不要
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
OTEL_TRACES_EXPORTER: "otlp",
OTEL_METRICS_EXPORTER: "otlp",
OTEL_LOGS_EXPORTER: "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を展開する
options: { env: { ...process.env, ...otelEnv } },
})) {
console.log(message);
}送信を確認するには、コレクターのログでスパン・メトリクス・ログイベントの着信を見るのが確実です。メトリクスならclaude_code.session.count(セッション開始時に必ず1回出るカウンター)を、ログイベントのみの構成ならclaude_code.user_promptイベントの着信を確認します。CLIはエクスポートエラーを既定で握りつぶします。エンドポイントに到達できなくても、あるいはコレクターがデータを拒否しても、エージェントは正常に動き続け、テレメトリだけが黙って失われます。エラーを表に出したい場合は、エクスポーター変数と一緒にCLAUDE_CODE_OTEL_DIAG_STDERR=1(Claude Code v2.1.179以降)を設定し、SDKのstderrコールバック(Python)またはstderrオプション(TypeScript)経由で診断ログを読みます。
短命な呼び出しでテレメトリを取りこぼさないための調整
CLIはテレメトリをバッチにまとめ、一定間隔でエクスポートします。プロセスが正常終了するときはCLIが保留データのフラッシュを試みますが、そのフラッシュ自体が短いタイムアウトで打ち切られるため、コレクターの応答が遅いとスパンが欠落することがあります。プロセスがCLIのシャットダウン前にkillされた場合は、バッファに残っていたデータがそのまま失われます。
既定のエクスポート間隔は、メトリクスが60秒、トレースとログが5秒です。単発タスクのように寿命の短い呼び出しでは、この間隔を縮めて実行中にデータを送り切ります。
OTEL_ENV = {
# ... 前節のエクスポーター設定 ...
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
"OTEL_LOGS_EXPORT_INTERVAL": "1000",
"OTEL_TRACES_EXPORT_INTERVAL": "1000",
}本番の長時間セッションでは間隔を既定値に戻し、短命な単発呼び出しでだけこの短縮設定を使い分けます。
トレースでエージェントループの内側を読む
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1を設定すると、エージェントループの各ステップがスパンとして記録されます。claude_code.interactionが1ターン全体を包み、その子としてclaude_code.llm_request(モデル名・レイテンシ・トークン数を属性に持つ)、claude_code.tool(権限待ちのclaude_code.tool.blocked_on_userと実行本体のclaude_code.tool.executionを子に持つ)、claude_code.hookが並びます。エージェントがAgentツールでサブエージェントを起動すると、そのサブエージェントのスパンも親のclaude_code.toolスパンの下にネストし、委譲の連鎖が1本のトレースにまとまります。
スパンには既定でsession.id属性が付きます。同じセッションに対して複数回query()を呼ぶ構成では、この属性でフィルタすれば1つのタイムラインとして追えます。
アプリケーション自身のトレースとつなげる
SDKはW3Cのトレースコンテキストを子プロセスへ自動で伝播します。アプリケーション側でOpenTelemetryのスパンがアクティブな状態でquery()を呼ぶと、SDKはTRACEPARENTとTRACESTATEを子プロセスの環境変数に注入し、CLIがそれを読んでclaude_code.interactionスパンをアプリ側スパンの子にします。エージェントの実行が独立したルートスパンとしてではなく、アプリケーションのトレースの一部として現れます。ただしClaude Code v2.1.212以前ではメトリクスのイベントログにtrace_id/span_idが付与されないため、ログとトレースを突き合わせて追う構成ではCLIのバージョンを先に確認してください。
TRACEPARENTをoptions.envに明示的に設定すると、この自動注入はスキップされます。任意の親コンテキストを固定したいときに使えます。対話的なCLIセッションは受け取ったTRACEPARENTを無視しますが、Agent SDKとclaude -pの実行だけはこれを尊重します。
Bashツールを介して実行したコマンドが自前でOpenTelemetryスパンを出す場合、そのスパンはclaude_code.tool.executionスパンの下にネストします。CLIがコマンドへTRACEPARENTを転送しているためです。
マルチテナントアプリでエンドユーザーを特定する
CLIは呼び出しに使った認証情報に基づいて、身元属性をすべてのイベントに付けます。1つのデプロイで多数のエンドユーザーにサービスを提供するアプリケーションでは、この属性はアプリ側の認証情報を指すだけで、エージェントが誰の代理で動いたかを表しません。
ツール呼び出しやMCPアクティビティをエンドユーザー単位に紐づけたい場合は、各query()呼び出しでエンドユーザーの識別子をリソース属性として注入します。OTEL_RESOURCE_ATTRIBUTESはカンマ・スペース・等号を予約文字として扱うため、値は事前にパーセントエンコードします。
from urllib.parse import quote
options = ClaudeAgentOptions(
env={
# ... 有効化例のエクスポーター設定 ...
"OTEL_RESOURCE_ATTRIBUTES": f"enduser.id={quote(request.user_id)},tenant.id={quote(request.tenant_id)}",
},
)エンドユーザーの識別子を付けた状態では、tool_decision・tool_result・mcp_server_connection・permission_mode_changedの各イベント(claude_code.接頭辞のログレコードとしてエクスポートされる)が、ユーザー単位の監査証跡になります。これをSIEMへ転送する運用が現実的な使い道です。Grafana Lokiに集約したログをLogQLを書かずに検索する方法はGrafana LokiのMCPログ検索で扱っています。
機微データの記録範囲を制御する
テレメトリは既定では構造情報だけです。所要時間・モデル名・ツール名は毎回記録されますが、エージェントが読み書きした中身は既定では記録されません。以下の変数を立てると、記録範囲が段階的に広がります。
| 変数 | 追加される内容 |
|---|---|
OTEL_LOG_USER_PROMPTS=1 | 追加される内容プロンプト本文 |
OTEL_LOG_TOOL_DETAILS=1 | 追加される内容ツール入力引数(ファイルパス・シェルコマンド・検索パターン) |
OTEL_LOG_TOOL_CONTENT=1 | 追加される内容ツール入出力全文(既定60KBで切り詰め。トレース有効化が前提) |
OTEL_LOG_RAW_API_BODIES | 追加される内容Messages APIのリクエスト・レスポンス全文(切り詰め上限を調整するCLAUDE_CODE_OTEL_CONTENT_MAX_LENGTHはClaude Code v2.1.214以降) |
OTEL_LOG_RAW_API_BODIESだけは値の指定方法が2通りあります。1なら60KBで切り詰めたインライン記録、file:<dir>なら切り詰めなしでディスクに本文を書き出し、イベント側にはbody_refのパスだけが残ります。この変数を有効にすることは、上の3変数が明かす内容すべてに同意したことを意味します。会話履歴全体を含み、拡張思考の内容だけは編集済みで記録される点も踏まえ、観測パイプラインが機微データを保持できる承認を得ているときだけ有効にします。
Claude Code CLIの直接設定とどこが違うか
Claude Code CLIを直接使う場合の設定手順はClaude CodeのOpenTelemetryで利用量とコストを可視化にまとめていますが、Agent SDK経由では次の3点が変わります。
| 観点 | Claude Code CLI | Agent SDK |
|---|---|---|
| 設定の書き方 | Claude Code CLIシェルの環境変数・.bashrc等 | Agent SDKoptions.env(呼び出し単位)またはプロセス環境 |
| トレースの親子関係 | Claude Code CLI単独のルートスパン | Agent SDKアプリのスパンが親、claude_code.interactionが子(TRACEPARENT伝播) |
| エンドユーザー識別 | Claude Code CLIセッション単位が基本 | Agent SDKquery()呼び出しごとにenduser属性を注入できる |
app.entrypointという標準属性の値でも実行経路を区別できます。CLIから直接起動したセッションはcli、TypeScript SDK経由はsdk-ts、Python SDK経由はsdk-pyという値が入ります。同じコレクターにCLIとSDK両方のテレメトリを集約している組織では、この属性でダッシュボードのフィルタを分けられます。
よくあるつまずき
- 短命なワーカーでデータが欠ける: サーバーレス関数やCIジョブのように寿命の短いプロセスでは、既定のエクスポート間隔(メトリクス60秒)より先にプロセスが終了し、テレメトリが送られないまま失われます。エクスポート間隔を数秒まで縮め、可能ならプロセス終了前に明示的な待機を入れます。
options.envでprocess.envを展開し忘れる: TypeScriptのoptions.envは継承環境を丸ごと置き換えます。...process.envを展開せずにOTel変数だけを渡すと、PATHやANTHROPIC_API_KEYまで消えて別の失敗を招きます。- consoleエクスポーターを選んでSDKの出力と衝突させる: SDKは標準出力をメッセージチャネルとして使うため、
consoleエクスポーターと同じ経路を奪い合います。ローカル確認は必ずローカルコレクター経由にします。 - 失敗が見えないまま気づかない: エクスポート失敗は既定で無音です。本番投入前に
CLAUDE_CODE_OTEL_DIAG_STDERR=1で一度診断ログを確認し、エンドポイントと認証ヘッダーが正しいことを確かめてから常用構成に切り替えます。 - TRACEPARENTの伝播をあてにしすぎる: 対話的なCLIセッションは受け取った
TRACEPARENTを無視します。伝播が効くのはAgent SDKとclaude -pの実行だけという前提を、社内ドキュメントにも明記しておきます。
まとめ
Agent SDKのテレメトリは、SDK自身ではなく子プロセスのCLIが計装しています。CLAUDE_CODE_ENABLE_TELEMETRY=1とシグナルごとのエクスポーター指定さえ揃えれば、コストの集計を応答ストリームから読む方法とは別に、外部の観測基盤へトレース・メトリクス・ログを流し込めます。本番運用で押さえるべきは、エクスポート間隔の調整、TypeScriptでの環境変数の扱い、そしてエンドユーザー識別子の注入によるマルチテナント監査の3点です。CLIを直接使うチームとAgent SDKでエージェントを組み込むチームが同じ組織にいるなら、app.entrypoint属性でダッシュボードを分けておくと運用の混乱を避けられます。