
Claude Code のテレメトリを Grafana otel-lgtm で可視化してみた
はじめに
こんにちは!ひろたこです。
今回は Claude Code の OpenTelemetry(OTel)テレメトリを、ローカルに立てた grafana/otel-lgtm オールインワンコンテナで丸ごと可視化してみました!
Claude Code を毎日使っていて、「今日いくら使った?」「何のリクエストにいくらかかってる?」と聞かれて即答できますか?私はできませんし、正直サブスクなので気にしていませんでした。Claude Code は環境変数を数個設定するだけでメトリクス・ログ・トレースの3シグナルを OTLP でエクスポートできるので、「中で何が起きているか」を実際に覗いてみました!
この記事でわかることは次の3つです。
- Colima +
grafana/otel-lgtmで、Docker Desktop なしにテレメトリ可視化環境を作る手順 - Grafana(Tempo / Loki / Prometheus)で Claude Code の3シグナルをどう見るか
- 実データ分析で見つけた落とし穴:頼んでいないはずの haiku が最多リクエストだった理由と、モデル比較に
query_sourceフィルタが必須な話
Claude Code のテレメトリとは
Claude Code には OpenTelemetry 準拠のテレメトリ機能が組み込まれており、環境変数だけで有効化できます。コードの改修やプラグインの導入は不要です。
- メトリクス・ログ(イベント)は
CLAUDE_CODE_ENABLE_TELEMETRY=1で有効化 - トレースはベータ機能で、
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1を追加で指定 - デフォルトではプロンプト本文やツールの中身は送信されません(イベント名・モデル名・トークン数などの構造情報のみ)。内容まで記録したい場合だけ、専用の環境変数でオプトインする設計です
詳細は公式ドキュメントにまとまっています。
有効化に関する詳細な記事はこちらです。
grafana/otel-lgtm とは
grafana/otel-lgtm は、Grafana が提供する検証用オールインワンコンテナです。コンテナ1つの中に以下が全部入っています。
| コンポーネント | 役割 |
|---|---|
| OTel Collector | OTLP でテレメトリを受信 |
| Prometheus | メトリクスの保存 |
| Loki | ログの保存 |
| Tempo | トレースの保存 |
| Grafana | 可視化 UI |

(図は grafana/docker-otel-lgtm 公式リポジトリより引用)
「OTel の3シグナルを受けて見る」ための環境が docker run 一発で手に入るので、今回のような検証にぴったりです。
環境・前提条件
検証は 2026年7月20日〜21日に実施しました。
| 項目 | バージョン等 |
|---|---|
| マシン | macOS 26.5.2(Apple Silicon / darwin/arm64) |
| Colima | 0.10.3(依存: lima 2.1.4) |
| Docker CLI(Mac 側) | 29.6.2 |
| Docker Engine(Colima VM 内 / linux/arm64) | 29.5.2 |
| docker-compose | 5.3.1 |
| grafana/otel-lgtm | latest(digest sha256:b4da8bf6b1b44f6f683883869619eb9781465c642ce5f2d70591fb27094ddb1c) |
| Claude Code | 2.1.215 |
やったこと
1. Colima と Docker CLI のインストール
まずは Homebrew で Colima・Docker CLI・docker-compose をインストールします。
$ brew install colima docker docker-compose
私の環境ではインストール完了まで約2分でした。Docker Desktop を入れなくても、これだけで Docker 環境の材料が揃います。
2. Colima の起動
VM を起動します。CPU とメモリは控えめに指定しました。
$ colima start --cpu 2 --memory 4
INFO[0000] starting colima
INFO[0000] runtime: docker
INFO[0000] creating and starting ... context=vm
INFO[0001] downloading disk image ... context=vm
INFO[0055] provisioning ... context=docker
INFO[0057] starting ... context=docker
INFO[0057] done
初回は VM のディスクイメージ取得が入りますが、実測では start 完了まで約1分でした。CPU/メモリの指定は初回だけ記憶されるので、次回以降は colima start だけで OK です。
動作確認は docker version で行います。ここでのポイントは、Client(Mac 側 CLI 29.6.2)と Server(VM 内エンジン 29.5.2)の2ブロックが両方表示されれば成功、ということです。Client 側の Context: colima が接続先の印になっています。Colima が未起動だと Server ブロックが「Cannot connect to the Docker daemon」エラーになるので、切り分けもしやすいです。
3. otel-lgtm コンテナの起動
いよいよ otel-lgtm を起動します。
$ docker run -d --name lgtm \
-p 3000:3000 -p 4317:4317 -p 4318:4318 \
grafana/otel-lgtm
公開しているポートの役割は以下の通りです。
| ポート | 用途 |
|---|---|
| 3000 | Grafana UI |
| 4317 | OTLP gRPC 受信口 |
| 4318 | OTLP HTTP 受信口 |
起動できたかログで確認します。
$ docker logs lgtm 2>&1 | tail -5
- 4318: OpenTelemetry HTTP endpoint
- 3000: Grafana (http://localhost:3000). User: admin, password: admin
- 3200: Tempo endpoint
- 4040: Pyroscope endpoint
- 9090: Prometheus endpoint
ログに「The OpenTelemetry collector and the Grafana LGTM stack are up and running.」と出れば準備完了です。http://localhost:3000 を開くと Grafana にアクセスできます(初期ユーザー・パスワードは admin / admin)。
4. Claude Code のテレメトリ設定
Claude Code 側の設定は環境変数だけです。otel-claude.env というファイルにまとめました。全文貼っておきます。
# テレメトリ有効化(必須)
export CLAUDE_CODE_ENABLE_TELEMETRY=1
# トレース(ベータ)有効化 — 今回の主目的
export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
# 3シグナルすべて OTLP でエクスポート
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_TRACES_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
# 検証用: エクスポート間隔を短縮(すぐ Grafana に反映される)
export OTEL_METRIC_EXPORT_INTERVAL=5000
export OTEL_LOGS_EXPORT_INTERVAL=1000
export OTEL_TRACES_EXPORT_INTERVAL=1000
# --- 以下はコンテンツ送信のオプトイン(必要なときだけコメントを外す) ---
# export OTEL_LOG_USER_PROMPTS=1 # プロンプト本文を含める
# export OTEL_LOG_ASSISTANT_RESPONSES=1 # モデル応答テキストを含める
# export OTEL_LOG_TOOL_DETAILS=1 # ツール入力引数を含める
エクスポート間隔をデフォルトから短縮(メトリクス5秒・ログ/トレース1秒)しているのは検証用の設定です。反映をすぐ確認できるほか、短命なプロセスがエクスポート前に終了してデータが欠落するのを防ぐ意味もあります。
使い方は source してから Claude Code を起動するだけです。
$ source ./otel-claude.env && claude
5. テレメトリ有効で同一タスクを3モデルで実行
テレメトリを有効にした状態で、同じタスクを model = fable / opus / sonnet の3セッション(対話モード)で実行しました。
- タスク: 「Zed エディタの使い方を調べてみて簡潔に分かりやすくまとめてください。」

ちょっとした検証テクニックですが、ツールの許可プロンプトをわざと少し待ってから許可すると、claude_code.tool.blocked_on_user スパン(権限確認の待ち時間)が育って、トレースのウォーターフォールが見応えのあるものになります。
6. Grafana で3シグナルを確認する
Grafana の Explore から、シグナルごとにデータソースを切り替えて確認します。
| シグナル | 見方 |
|---|---|
| トレース | Explore → Tempo → Search で claude_code.interaction を検索 → ウォーターフォールで1ターンの内訳を見る |
| ログ(イベント) | Explore → Loki → tool_decision / user_prompt などのイベントを検索 |
| メトリクス | Explore → Prometheus → claude_code_token_usage などで検索 |
Tempo のウォーターフォールはこんな感じです。

ウォーターフォールは棒の左端が開始タイミング、長さが所要時間を表します。並行実行しているところと、何かを待っているところが視覚的にわかるのが気持ちいいです!
Claude Code から出てくる主なテレメトリは以下の通りです。
- メトリクス:
claude_code.session.count/claude_code.token.usage/claude_code.cost.usage/claude_code.lines_of_code.count/claude_code.commit.count/claude_code.active_time.total - イベント(ログ):
claude_code.user_prompt/claude_code.api_request/claude_code.api_error/claude_code.tool_result/claude_code.tool_decision/claude_code.mcp_server_connectionなど - スパン(ベータ):
claude_code.interaction→claude_code.llm_request/claude_code.tool(→blocked_on_user/execution)
見えたもの(分析)
環境ができたので、収集したデータを分析していきます。ここからが本番です!
分析1: モデル別の api_request 集計
Loki に届いた api_request イベントをセッション全体で集計しました。

| モデル | リクエスト数 | 合計コスト | API合計時間 | 出力トークン | キャッシュ読取 |
|---|---|---|---|---|---|
| claude-fable-5 | 8回 | $0.758 | 69.2s | 3,044 | 256,714 |
| claude-opus-4-8 | 4回 | $0.243 | 35.6s | 1,868 | 110,623 |
| claude-haiku-4-5 | 11回 | $0.219 | 79.1s | 6,036 | 0 |
| claude-sonnet-5 | 6回 | $0.216 | 34.4s | 2,119 | 249,871 |
リクエスト単位のコスト・所要時間・トークン数がここまで見えるだけでも十分収穫なのですが…この表、何かおかしくないですか?
私は haiku にタスクを1つも与えていません。 実行したのは fable / opus / sonnet の3セッションだけです。それなのに、リクエスト数の最多は haiku の11回。しかもキャッシュ読取は0です。
分析2: 頼んでいない haiku が最多リクエストだった理由
正体を突き止めるため、api_request イベントに付いている query_source 属性でクロス集計してみました。
| モデル | query_source | 回数 | 意味 |
|---|---|---|---|
| fable-5 | repl_main_thread | 6 | 本体のタスク処理 |
| sonnet-5 | repl_main_thread | 5 | 本体のタスク処理 |
| opus-4-8 | repl_main_thread | 3 | 本体のタスク処理 |
| haiku-4-5 | web_search_tool | 6 | WebSearch ツールの内部処理 |
| haiku-4-5 | generate_session_title | 3 | セッションタイトル自動生成 |
| haiku-4-5 | web_fetch_apply | 2 | WebFetch の内容処理 |
| 各モデル | away_summary / prompt_suggestion | 各1 | 補助処理 |
謎が解けました。haiku の11回はすべて Claude Code 内部の補助呼び出しで、タスクは1回も処理していません。 WebSearch の内部処理に6回、セッションタイトルの自動生成に3回、WebFetch の内容処理に2回。Claude Code は、ユーザーが指定したモデルとは別に、ツールの内部処理やタイトル生成などで haiku を自動的に使っていたわけです。
これはテレメトリの生データを眺めていなければ気づけなかった発見でした。そしてここから重要な教訓が導けます。
モデル同士を比較したいなら、query_source="repl_main_thread" でのフィルタが必須です。フィルタせずに集計すると、内部の補助呼び出しが混ざって比較が歪みます。
Loki でそのまま使える LogQL クエリを置いておきます。まず本体リクエストだけを抽出するクエリです。
{service_name="claude-code"} | event_name="api_request" | query_source="repl_main_thread"
モデル別の時系列集計はこちらです。
sum by (model) (count_over_time({service_name="claude-code"}
| event_name="api_request" | query_source="repl_main_thread" [5m]))

ちなみに、本体リクエストの query_source は実行形態によって変わります。対話モードは repl_main_thread、ヘッドレス実行(claude -p)は sdk になるので、集計時は自分の実行形態に合わせてください。
まとめると、評価は2段構えにするのがよさそうです。
- モデル自体の比較:
repl_main_threadのみで集計する(純粋な本体の働きを見る) - コスト比較: セッション合計で集計する(内部呼び出しも「そのモデルに任せたときの総費用」に含まれるため)
分析3: 補正後の本体リクエスト数
repl_main_thread だけに絞った「本体の手数」はこうなりました(各モデル1試行)。
| モデル | 手数(本体リクエスト数) |
|---|---|
| opus-4-8 | 3回 |
| sonnet-5 | 5回 |
| fable-5 | 6回 |
opus が最少手数でタスクを終えています。ただし各モデル1試行のみなので、この差がモデルの実力なのかばらつきなのかは区別できません。ここから結論を出すのは早計です。この「ちゃんと比較したい」という欲求が、次の検証(モデル評価実験)につながります。
分析4: そのほかに見えたもの
権限判断(tool_decision イベント) も面白かったです。今回のセッションでは、WebFetch 7回・WebSearch 6回の計13回すべてが「その場で手動許可(user_temporary)」でした。つまりこの2つのツールは allowlist への追加候補だと、データが教えてくれています。一方 ToolSearch の3回は config(設定で自動許可)でした。テレメトリがそのまま設定改善の入口になる実用例ですね。
トレースは計8件収集できました(ルートスパン claude_code.interaction が5件、claude_code.llm_request が3件)。interaction の下に llm_request と tool がぶら下がり、tool の下に blocked_on_user(権限確認待ち)と execution(実行)が分かれる構造で、「1ターンの時間がどこに消えているか」を追える形になっています。スパンの詳細な時間分析は続編でやる予定です。
ハマったポイント
今回もいくつかハマったので正直に共有します。同じことを試す方の参考になれば!
再開時に docker run を再実行して Conflict エラー
検証を再開しようとして docker run をもう一度実行したら、エラーになりました。
The container name "/lgtm" is already in use
docker run は新規作成コマンドなので、停止中の既存コンテナと名前が衝突します。分かっていたつもりでも、久しぶりに再開するとうっかりやりがちですww 2回目以降は docker start lgtm が正解です。既存コンテナの再開なので、収集済みのデータもそのまま復活します。
再開まわりの運用コマンドをまとめておきます。
# 一時中断(データを残す)
colima stop
# 再開
colima start # CPU/メモリ設定は記憶済み
docker start lgtm # 既存コンテナの再開
# 完全撤収(データも消える)
docker rm -f lgtm && colima stop
docker logs は停止中のコンテナでも表示される
docker logs lgtm はコンテナが停止していても過去ログを表示します。ログが見えたから起動している、とは限りません。「ログは出るのにデータが来ない」ときはまずここを疑って、起動確認は docker ps の STATUS で行いましょう。
Colima 再起動でコンテナは自動復帰しない
Colima を停止するとコンテナは Exited (255) になり、colima start してもコンテナは自動再開しません(restart policy が no のため)。再開のたびに docker start lgtm が毎回必要です。
メトリクスが Prometheus に届かない(未解決)
今回一番悩ましかったのがこれです。メトリクスだけが Prometheus で検索できませんでした。
切り分けたところ、Collector までは届いている(受信176ポイント)ものの、Collector → Prometheus の転送が649ポイント全件失敗しており、claude_code_* 系のメトリクスが Grafana から見えない状態でした。
幸い、トークン量やコストは Loki 側の api_request イベントから取得できるため、検証自体は継続できました(分析1〜3はすべて Loki ベースです)。切り分け候補としては、OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative を env に追加しての再実行を考えています。この件は未解決のままなので、解決できたら追記します。
そのほかの注意点
- otel-lgtm はデータをコンテナ内に持つため、
docker rmすると収集データも消えます。検証用途なら許容範囲ですが、消したくないデータがあるうちはdocker stop/docker startで運用しましょう - ヘッドレス実行のような短命プロセスでは、エクスポート間隔より早くプロセスが終了するとデータが欠落しえます。手順4の env でエクスポート間隔を短縮(メトリクス5秒・ログ/トレース1秒)しているのはこの対策も兼ねています
結果
3シグナルで何がどこまで見えたか、まとめます。
| シグナル | バックエンド | 今回見えたもの |
|---|---|---|
| ログ(イベント) | Loki | api_request(モデル・コスト・所要時間・トークン・query_source)、tool_decision(権限判断13回の内訳)、user_prompt など。今回の分析の主戦場 |
| トレース(ベータ) | Tempo | claude_code.interaction 配下のウォーターフォール(計8件)。blocked_on_user で権限確認の待ち時間も分離して見える |
| メトリクス | Prometheus | 今回は Collector → Prometheus の転送失敗により未確認(コスト・トークンは Loki の api_request で代替可能) |
まとめ
今回は Claude Code のテレメトリを grafana/otel-lgtm で可視化し、実データを分析してみました!
わかったこと:
- Claude Code のテレメトリは環境変数だけで有効化でき、
grafana/otel-lgtmならdocker run一発で受け皿が作れる。思っていたよりずっと手軽でした - Claude Code は指定モデルとは別に、内部の補助処理(WebSearch・タイトル生成など)で haiku を自動使用する。今回のデータでは haiku の11リクエストすべてが内部呼び出しでした
- そのためモデル比較には
query_source="repl_main_thread"フィルタが必須。コスト比較はあえてフィルタせずセッション合計で見る、という2段構えがおすすめです tool_decisionイベントから allowlist 追加候補(今回なら WebFetch と WebSearch)がデータで分かる。テレメトリは設定チューニングの入口にもなります- メトリクスの Prometheus 転送は未解決の課題として残りました。Loki のイベントで代替できたとはいえ、ここは追って解決したいところです
個人的に一番の収穫は、「頼んでいない haiku が最多リクエスト」という違和感から query_source にたどり着いた過程でした。集計表を鵜呑みにせず属性で掘る、という可観測性の基本を Claude Code 自身を題材に体験できたのは面白かったです。
そして分析3で書いた通り、各モデル1試行では手数の差から結論は出せません。**次回はテレメトリ×LLM採点でモデル比較を客観化する予定です。**続編もぜひ読んでいただけると嬉しいです!
この記事が誰かの助けになれば幸いです。
最後まで読んでいただき、誠にありがとうございました。








