
Claude Codeの利用トレンドを掴むCloudWatch DashboardsをCDKで簡易に構築する
はじめに
コーディングエージェントの普及で、サブエージェントの並列実行や多段ワークフローなど、トークンを注ぎ込むプラクティスが広がりました。これを支えてきたのは消費量を気にせず使える定額プランでしたが、その前提は変わりつつあります。Claude Enterprise は 2025 年 11 月からシート料金とトークン消費を分離して標準 API 料金の従量課金へ、GitHub Copilot も 2026 年 6 月から回数制の Premium Request を廃止して GitHub AI Credits の従量課金へ移行しました。
従量課金になると消費量が気になりますが、これを削ること自体を目的にすると、トークンを注いで得られたはずの生産性まで手放しかねません。見たいのはむしろ、投じたトークンがどのモデル・スキルに流れ、ツール実行がどれだけ失敗しているかという運用の実態です。
もっとも、開発生産性の計測には DORA や SPACE といったフレームワークが定着している一方、指標は目標に据えた途端に数字合わせを招きやすいという難しさもあります。ここでの狙いも評価ではなく、あくまでチームの運用実態を把握することです。そこで欲しくなるのが「チーム単位で Claude Code の利用状況を観測する基盤」です。
Claude Code には OpenTelemetry ベースのテレメトリ送出機能が組み込まれており、環境変数をいくつか設定するだけで利用状況を OTLP で送れます。問題は集約先です。今回は SigV4 署名を Hono(Lambda)のプロキシに肩代わりさせ、ローカルに常駐プロセスを一切置かず CDK スタック 1 つのデプロイで完結するテンプレートを作りました。プロキシは API Gateway で集約し、認証は API Gateway の API キー機能に任せています。
投じたトークンが見合っているかの判断や改善の打ち手は別の記事に譲り、以降はこのテンプレートのアーキテクチャと設計上の判断を紹介します。
セットアップ(最初の手順)
まずは動かすところまでの手順です。このテンプレートは常駐プロセスを置かず CDK スタック 1 つのデプロイで完結するので、前提となる Node.js 24+ / pnpm / AWS CLI の認証設定さえ済んでいれば、流れは次のようになります。
pnpm installで依存をインストールするiac/awsに移動し、初回のみpnpm cdk bootstrapしてからpnpm deployでスタック(ダッシュボード×2 + ロググループ + OTLP プロキシ)を展開する- デプロイ完了後、Outputs に出る
OtlpEndpointとApiKeyIdを控える aws apigateway get-api-keyで API キーの値を取得する.claude/settings.local.jsonを作成し、OTEL_EXPORTER_OTLP_ENDPOINTとx-api-keyを設定する- Claude Code をこのプロジェクトで再起動する
コマンドの実体は README のセットアップ節にまとまっています。
テレメトリ設定の置き場所
Claude Code に送出先を教える設定は、秘匿かどうかで 2 ファイルに分かれます。エクスポーター種別・プロトコル・temporality といった非秘匿のテレメトリ設定は .claude/settings.json に置き、リポジトリにコミットして共有します。
一方、送出先エンドポイントと API キーは秘匿情報なので .claude/settings.local.json(gitignore 対象)に分離します。.claude/settings.local.json.example を雛形にして、OTEL_EXPORTER_OTLP_ENDPOINT を Outputs の OtlpEndpoint に、x-api-key を先ほど取得した API キーの値に書き換えます(値そのものはコミットしません)。
なお API とダッシュボードは全プロジェクト共用なので、既存プロジェクトへ組み込む場合も .claude/settings.json の env と .claude/settings.local.json を転記するだけで済みます。
チームで使う場合(API キーを Config で管理)
テンプレートの初期状態は、API キーを 1 本だけ作って usage plan に紐づける最小構成です。
チームで配る場合は、この 1 本を全員で共有するより、CDK を拡張してメンバー(またはチーム)ごとに API キーを発行するのがおすすめです。config.ts の Config にメンバー一覧を持たせ、その配列を回して addApiKey し、同じ usage plan に束ねます。こうしておけばキーが漏れたメンバーの分だけ usage plan から失効でき、全体を作り直さずに済みます(WAF の IP 制限と併用すればさらに堅くなります)。
ダッシュボード構成
サマリ
デプロイすると、用途の違う 2 枚のダッシュボードと、Logs Insights の保存クエリ群ができます。
| ダッシュボード | 用途 | データ元 |
|---|---|---|
claude-code-ops |
直近の運用・診断 | OTLP ストア(PromQL)+ /claude-code/events(Logs Insights) |
claude-code-trends |
長期トレンド | Classic メトリクス(Metric Math) |
このほか、claude-code/ フォルダに daily-cost / daily-token-usage / tool-usage-summary / skill-activations / skill-cost / api-errors といった Logs Insights の保存クエリを置いています。
2 枚に分けているのは、見たい時間軸が違うためです。ops は直近を細かい粒度で追う用途で、データ元の PromQL が 1 クエリ最大 7 日のため月次は描けません。trends は日次・月次の粗い粒度で、窓を長く取らないとトレンドが現れません。この二層構成の背景は後述の Embedded Metric Format(EMF)の節で説明します。
claude-code-ops
直近の運用・診断用です。Cost・トークンは OTLP ストアを PromQL で、スキル利用・ツール失敗・API エラーは OTLP メトリクスに存在しないためイベントを Logs Insights で集計します。
運用上の制約が 2 つあります。ひとつは PromQL で、1 クエリの時間範囲は最大 7 日(実測のハードリミットは 8 日)なので、ops は直近ビュー専用と割り切っています。もうひとつは Logs Insights の課金で、クエリはスキャンしたログ量に応じて課金されるため、常設ウィジェットも開くたびに費用がかかります(期間や対象を絞る配慮が要ります)。
| ウィジェット | 日本語での表現 | 種別 | クエリ |
|---|---|---|---|
| Cost (USD, team total) | コスト(USD、チーム合計) | PromQL | sum(increase({"claude_code.cost.usage", "@resource.service.name"="claude-code"}[15m])) |
| Token usage (by type) | トークン使用量(種別ごと) | PromQL | sum by (type)(increase({"claude_code.token.usage", "@resource.service.name"="claude-code"}[15m])) |
| Token usage (by model) | トークン使用量(モデルごと) | PromQL | sum by (model)(increase({"claude_code.token.usage", "@resource.service.name"="claude-code"}[15m])) |
| Skill activations (by skill) | スキル起動回数(スキルごと) | Logs Insights | filter body = 'claude_code.skill_activated' | stats count(*) as activations by attributes.skill.name as skill | sort activations desc | limit 25 |
| Skill cost (USD, by skill) | スキル別コスト(USD) | Logs Insights | filter body = 'claude_code.api_request' and ispresent(attributes.skill.name) | stats sum(attributes.cost_usd) as cost_usd, sum(attributes.output_tokens) as output_tokens by attributes.skill.name as skill | sort cost_usd desc | limit 25 |
| Tool failure rate (%) | ツール失敗率(%) | Logs Insights | filter body = 'claude_code.tool_result' | stats sum(attributes.success = 'false') * 100.0 / count(*) as failure_pct by bin(1h) |
| Tool failures (recent) | ツール失敗の詳細(直近) | Logs Insights | fields @timestamp, attributes.tool_name, attributes.error_type, attributes.duration_ms | filter body = 'claude_code.tool_result' and attributes.success = 'false' | sort @timestamp desc | limit 50 |
| API errors (recent) | API エラー(直近) | Logs Insights | fields @timestamp, attributes.model, attributes.error, attributes.status_code | filter body = 'claude_code.api_error' | sort @timestamp desc | limit 50 |

claude-code-trends
長期トレンド用です。EMF 派生の Classic メトリクスを日次(period 1 日 / 統計 Sum)で参照します。Tool failure rate だけは Metric Math で、ツール呼び出しの無い日でも比率が壊れないよう FILL(x, 0) で 0 埋めしています。
| ウィジェット | 日本語での表現 | 種別 | メトリクス / 計算式 |
|---|---|---|---|
| Cost (USD, daily) | コスト(USD、日次) | メトリクス (Sum) | メトリクス Cost を Sum |
| Tokens by type (daily) | トークン(種別ごと、日次) | メトリクス (Sum) | メトリクス Tokens(dimension type = input / output / cacheRead / cacheCreation)を Sum |
| Tool failure rate (%, daily) | ツール失敗率(%、日次) | Metric Math | 100 * FILL(num, 0) / FILL(d0, 0)(num = ToolFailures Sum、d0 = ToolCalls Sum) |
長期のトレンドのため、データ数がまだ少ないです 🙇(※ 1週間後また掲載します)

メトリクス・イベント一覧
メトリクス・イベントの定義は Claude Code 公式ドキュメントの Monitoring ページに基づきます(属性やイベントは更新される可能性があるため、正確な一覧は公式を参照してください)。
Claude Code が送出する metrics / logs 一覧
メトリクス(/v1/metrics)
| メトリクス | 内容 | 主な属性 |
|---|---|---|
claude_code.cost.usage |
API 換算コスト (USD) | model |
claude_code.token.usage |
トークン数 | type (input/output/cacheRead/cacheCreation), model |
claude_code.session.count |
セッション開始数 | |
claude_code.lines_of_code.count |
変更コード行数 | type (added/removed) |
claude_code.commit.count |
コミット数 | |
claude_code.pull_request.count |
PR 作成数 | |
claude_code.code_edit_tool.decision |
編集ツールの受入判定 | decision (accept/reject), tool |
claude_code.active_time.total |
アクティブ時間 (秒) |
イベント / logs(/v1/logs)
body にイベント完全名、attributes に詳細が入ります(数値・boolean 属性は文字列で届く)。
| イベント | 内容 |
|---|---|
claude_code.api_request |
API 呼び出し 1 回。model / cost_usd / 各種トークン数 / skill.name 等 |
claude_code.api_error |
API エラー。error / status_code |
claude_code.user_prompt |
プロンプト送信。本文は既定でリダクション |
claude_code.assistant_response |
アシスタント応答 |
claude_code.tool_result |
ツール実行結果。tool_name / success / duration_ms / error_type |
claude_code.tool_decision |
ツール実行の許可判定 |
claude_code.skill_activated |
スキル発動。skill.name / invocation_trigger / skill.source |
claude_code.hook_registered / hook_execution_start / hook_execution_complete |
フック登録・実行 |
claude_code.mcp_server_connection |
MCP サーバ接続 |
claude_code.at_mention |
@ メンション |
アーキテクチャ
全体像
全体像は下図の通りです。Claude Code の組み込み OTel エクスポーターが API Gateway(api key + WAF)へ OTLP を送り、Hono on Lambda が SigV4 署名を付けて CloudWatch の各受け口へ素通し転送しつつ、同一ペイロードから EMF で Classic メトリクスを複製します。

API Gateway にプロキシを集約する理由
前提として、Claude Code が送出するのは metrics とイベントログの 2 シグナルで、traces は送出しません。シグナル別の AWS 側 OTLP 受け口と、認証方式ごとの東京(ap-northeast-1)対応は次のとおりです。
| シグナル | Claude Code の送出 | 受け口 (エンドポイント) | SigV4 | Bearer Token |
|---|---|---|---|---|
| metrics | ○ 送出する | CloudWatch Metrics (monitoring.*/v1/metrics) |
○ 東京可 | ○ 東京可 |
| logs | ○ 送出する(イベントログ) | CloudWatch Logs (logs.*/v1/logs) |
○ 東京可 | × 東京不可(US 4 リージョン限定 ※) |
| traces | × 送出しない | X-Ray (xray.*/v1/traces) |
○ 東京可 | × 非対応 |
※ us-east-1 / us-east-2 / us-west-1 / us-west-2
Hono(Lambda)のプロキシを挟む理由は以下です。
- Claude Code のエクスポーターは
OTEL_EXPORTER_OTLP_HEADERSの静的ヘッダーしか付けられず、AWS の OTLP 受け口が基本とする SigV4 署名を自力で行えない - 静的ヘッダーで使える Bearer Token 認証は logs では US 4 リージョン限定で、東京リージョンにはイベントログを直接送る手段がない
プロキシがこの SigV4 署名を肩代わりすることで、東京リージョンで完結させています。
EMF(Embedded Metric Format)を使う理由
プロキシを通ったテレメトリの格納先は 3 つです。
| 格納先 | 入るもの | クエリ手段 |
|---|---|---|
CloudWatch OTLP ストア (monitoring.*/v1/metrics へ転送) |
全メトリクス(素通し) | PromQL (1 クエリ最大 7 日) |
ロググループ /claude-code/events (logs.*/v1/logs へ転送, 13ヶ月) |
全イベント(素通し) | Logs Insights |
Classic メトリクス (EMF 派生, namespace claude-code, 15ヶ月) |
コスト・トークンなど選抜のみ | Metric Math |
CloudWatch の OTLP ストア(PromQL)には 1 クエリ最大 7 日という制約があるため、これだけでは月次のトレンドが引けません。そこでプロキシが同一ペイロードから EMF で Classic メトリクスを複製し、短期の運用・診断は PromQL、長期トレンドは Classic メトリクス(Metric Math)という二層構成で観測を担保しています。
ソース解説
プロキシ(Hono on Lambda)が OTLP を受けてやることは 2 つです。CloudWatch の OTLP エンドポイントへ SigV4 署名して素通し転送する(本体)のと、同じペイロードから EMF を出力して Classic メトリクス化する(長期観測、副次)ことです。エンドポイントごとに整理します。
metrics の転送(/v1/metrics)
EMF 派生(metricsToEmf)を出しつつ、monitoring.<region>.amazonaws.com/v1/metrics へ SigV4 署名して転送します。EMF 派生が失敗しても転送は止めません。
logs の転送(/v1/logs)
logs は転送前にサニタイズを挟みます。サニタイズ → EMF 派生(logsToEmf)→ logs.<region>.amazonaws.com/v1/logs へ SigV4 転送、の順です。宛先ロググループ / ストリームは x-aws-log-group / x-aws-log-stream ヘッダーでここで付与します(クライアント指定は不要)。
サニタイズ
OTEL_LOG_TOOL_DETAILS=1 を有効にすると tool_parameters(Bash コマンド本文やファイルパスを含む JSON)が乗ります。集計に必要な識別子(subagent_type / mcp_server_name / skill_name)だけを allowlist で残し、本文はロググループに入る前に落とします。別 Lambda の 2 段構成にせず、全ペイロードが必ず通るこのプロキシで転送前にインライン処理しているのがポイントです。
EMF 変換
EMF 化するのは 4 系列だけです。Cost / Tokens は metrics ペイロードから(metricsToEmf、対象メトリクスのみ拾う)、ToolCalls / ToolFailures は tool_result イベントから数えます(logsToEmf、success は文字列 "false" で届く)。
長期トレンドの Metric Math
ダッシュボード側では、上の ToolCalls / ToolFailures を Metric Math で合成して失敗率(%)を出します。ratePercent が FILL(x, 0) で欠測日を 0 に埋めるため、ツール呼び出しの無い日があっても比率が欠測・0 除算で壊れません(period は日次)。
ウィジェット側は、分子に ToolFailures、分母に ToolCalls を渡して呼び出すだけです。
さいごに
今回作ったのは、あくまで「チームの利用状況を見られる環境」までで、記事もその構成の紹介に留めました。まずは、どのモデル・スキルにどれだけのコストとトークンが流れ、ツール実行がどれだけ失敗しているかを、チーム集計で眺められる土台ができた状態です。どの指標が実運用で効くのかの精査はこれからで、今はまず見える状態を作ったところです🙇🙇🙇
Claude Code の利用可視化はすでに専用の SaaS も出てきている領域なので、本テンプレートはそれらを置き換えるものというより、SaaS を入れるまで(あるいは入れないチーム)が自前で手早く可視化を始めるための繋ぎ、という位置づけです。本格的な分析まで作り込むつもりはなく、それ以上は素直に商用 SaaS に任せるのが良い、というのが個人的な考えです。
もう少し予算をかけられる、あるいはビジュアル面を強化したい場合は、商用 SaaS の手前に Amazon Managed Service for Prometheus + Amazon Managed Grafana という選択肢もあります。前者に切り替えれば本記事で触れた PromQL の 7〜8 日制約が無くなり(保持期間の範囲で長期クエリできる)、後者でダッシュボードの表現力も上げられます。
関連して、AWS 公式ブログにも Claude Code を CloudWatch で可視化する構成が載っています。扱うのは metrics のみで(logsは対象外)、metrics だけなら Bearer Token で CloudWatch へ直接送信できるためプロキシは登場しません。ダッシュボードはユーザー別トークンやコード行数・コミット数・承認/却下率を出す構成になっています。
その繋ぎの範囲で、拡張を考えているのは次の 3 点です。
- 長期トレンドでのスキル利用: いまはスキル別の集計が
ops(Logs Insights)側だけで、trends(Classic メトリクス)には出していません。スキル起動やスキル別コストを EMF で Classic メトリクス化すれば長期でも追えます。ただしskill.nameは高カーディナリティで、Classic メトリクスは系列ごとに課金されるため、上位のみ / allowlist で系列を絞るのが前提になります。 - PR との連動: 投資対象として投じた分が見合っているかは消費量だけを見ても分かりません。GitHub Actions あたりから PR 数を同じ
claude-codenamespace に EMF で送り、既存の Cost / Tokens と Metric Math で並べて「1 PR あたりのコスト」のように眺める、といったところです(別記事で書きたいです)。 - 別エージェントとの運用: Claude Code 以外のコーディングエージェントのテレメトリも同じ基盤に載せる方向です。ただ個人的には需要はあまり無さそうな印象で、優先度は低めに見ています。
サンプルやアーキテクチャはFable及び、Opus 4.8を使って作成しています。チームに合わせて指標に合わせてカスタマイズしてみてください!FBも頂ければ対応します!また使っていくうちに微妙なところがありましたら追記します 🙇







