
otelHeadersHelper を使って Claude Code の OTLP 認証トークンを pass に保管し Grafana Cloud へのログ送信を確認するまで
コーヒーが好きな emi です。
Claude Code の利用状況を OpenTelemetry (OTel) 経由で Grafana Cloud に送り、可視化してみたいと思い立ちました。
というのも、以下の記事で
Claude Code の利用状況を OpenTelemetry (OTel) で可視化している人は多いと思います。
という一文を見て「もうみんな Claude Code のトークン消費や利用量を当たり前のように可視化しているものなのか」と危機感に駆られ、手を動かし始めました。
なお、本記事で扱うのは「Grafana Cloud にログを送信し、その内容を確認できるところまで」です。送られたログをもとにダッシュボードを作って可視化する部分は今回のスコープには含めていません。
なぜ Grafana Cloud で可視化するのか
ちなみに Claude Enterprise プランで使っている方は、設定から使用量を確認できます。
https://claude.ai/new#settings/usage

なぜ設定画面の使用量ではなく、わざわざ Grafana を持ち出して可視化するのかというと、設定画面の使用量は「今の数値」を確認するには十分でも
- 時系列でのトレンド比較(日次・週次の推移を追う)
- 他のログやメトリクスと同じダッシュボード上で横断的に見る
- しきい値を超えたときにアラートで気づく
といった、監視基盤として使い倒す場面には向きません。OTel 経由で送っておけば、こうした運用にそのまま乗せられる、というのが可視化する意義だと考えています。
可視化基盤の置き場所についても、参考記事を見ながらいくつか候補を検討しました。
| 候補 | 概要 | メリット | 個人利用での課題 |
|---|---|---|---|
| AWS 自前構築 | OTel Collector + Amazon Managed Prometheus/Grafana などを AWS 上に構築 | 既存の AWS 環境と請求を 1 本化できる、柔軟にカスタマイズ可能 | コレクターやストレージの構築が必要で、検証目的には構成もコストも重い |
| ローカル Docker(Prometheus + Grafana) | 自分の PC/WSL 上で Prometheus と Grafana をコンテナ起動 | 追加コストなし、手元で完結 | Collector・Prometheus・Grafana をゼロから自分で構築・設定する手間がかかる |
| Amazon Quick Sight | AWS の BI サービスである Quick Sight にデータを溜めて可視化 | 既存の AWS 上の BI 環境と統一できる | OTLP を直接受け付けるコネクタを持たないため、S3 や Redshift などにいったんデータを溜めてから可視化する構成が前提になり、別途データ基盤の構築が必要になる |
| Grafana Cloud の無料枠 | SaaS の Grafana Cloud に OTLP でログ・メトリクスを直接送信 | インフラ構築・運用が不要、無料枠内で少量のログを試せる | 上限を超えると有料化、既存 AWS 環境とは別管理になる |
Prometheus とは?
Prometheus と Grafana は役割が異なります。
- Prometheus: メトリクス(数値の時系列データ)を定期的に収集・保存する時系列データベース。
- Grafana: それ自体はデータを保存せず、Prometheus や Loki などのデータソースに問い合わせて画面上に表示する可視化レイヤー。
自前で OTLP を可視化する場合、保存を担う Prometheus(またはその互換ストア)と、表示を担う Grafana の両方が必要になります。今回使った Grafana Cloud の OTLP エンドポイントは、この保存部分を Grafana Mimir としてあらかじめ用意してくれているため、利用者自身が Prometheus を構築しなくて済む、という位置づけです。
Grafana Mimir は Grafana Labs が開発した OSS で、Prometheus と完全互換の長期メトリクスストレージとして設計されています。Grafana Cloud は「Prometheus 互換のストレージ(Mimir)+ Grafana」をマネージドサービスとして提供している、というわけです。
Grafana Mimir is an open source software project that provides long-term storage for Prometheus and OpenTelemetry metrics.
今回はあくまで「自分の Claude Code の使い方を日々ちょっと覗ければ十分」という規模感だったため、インフラの構築・運用コストをかけずに少量のログ・メトリクスを無料で試せる Grafana Cloud の無料枠だけで完結する構成を選びました。認証トークンの保管には、以前の記事「WSL で Claude Skills が使えるようになるまでの奮闘記録」で構築した WSL 上の pass(GPG 暗号化ストア)をそのまま流用し、Claude Code の otelHeadersHelper(ヘッダーを動的生成する仕組み)経由でトークンを渡す方針です。
動作環境
| 項目 | バージョン |
|---|---|
| Windows | 10.0.26100.8655 |
| WSL | 2.7.10.0 |
| ディストリビューション | Ubuntu 22.04.5 LTS(Jammy Jellyfish) |
| Claude Code | 2.1.199 |
| pass | 1.7.4 |
| GnuPG | 2.2.27 |
pass と GPG エージェント(パスフレーズキャッシュ8時間)は、以前の記事で構築済みのものをそのまま使っています。
また、本題に入る前に、Claude Code のログイン方式を API Billing から Claude Enterprise に切り替えました。本記事の趣旨とはずれますのでトグルにしまっておきます。
Claude Code のログイン方式を API Billing から Claude Enterprise に切り替える
もともと手元の Claude Code は Anthropic Console の API キーによる従量課金(API Usage Billing)でログインしていましたが、今回は会社の Claude Enterprise プランに紐づけて利用状況を追いたかったため、先にログイン方式を切り替えました。

API Usage Billing が切り替え前の状態です。ここで /login を実行すると、ブラウザで OAuth 認証フローが始まります。
/login
まず「Claude Code さんが Claude chat account への接続を希望しています」という確認画面が出るので、「承認する」をクリックします。

セキュリティ保護のため、続けて再度サインインを求められました。

会社の Google Workspace アカウントでログインしているため「Googleで続ける」を選び、

アカウントを選択し、

「次へ」で許可すると、

最後に認証コードが表示されるので、これをコピーして Claude Code のターミナルに貼り付けます。

貼り付けると /login が Login successful で完了し、ステータス行の表示も API Usage Billing から Claude Enterprise に変わりました。

claude.ai 側の組織切り替えメニューでも、個人用の Free プランではなく会社の Enterprise 組織が選択されていることを確認できました。

これで、以降に送信する OTel テレメトリが正しい組織のセッションに紐づく状態になりました。
前提: otelHeadersHelper とは
Claude Code の公式ドキュメントによると、otelHeadersHelper は OTLP 送信用の認証ヘッダーを動的に生成する仕組みです。
動的認証が必要なエンタープライズ環境では、ヘッダーを動的に生成するスクリプトを設定できます。
~/.claude/settings.json のトップレベルにスクリプトのパスを指定しておくと、Claude Code が起動時と、以降29分ごとに再実行してヘッダーを取得します。
{
"otelHeadersHelper": "/bin/generate_opentelemetry_headers.sh"
}
スクリプトは以下のような、HTTP ヘッダー名と値のペアを表す JSON を1行で標準出力に返す必要があります。
#!/bin/bash
echo "{\"Authorization\": \"Bearer $(get-token.sh)\", \"X-API-Key\": \"$(get-api-key.sh)\"}"
OTEL_EXPORTER_OTLP_HEADERS に平文でトークンを書く代わりにこれを使えば、settings.json にも .bashrc にも認証情報を平文で残さずに済みます。これを Grafana Cloud 向けに実装するのが今回のゴールです。
Grafana Cloud 側の準備
アカウントとスタックの作成
Grafana Cloud のアカウントをまだ持っていなかったため、まずはアカウントとスタックを作成するところから始めました。サインアップ後のアカウント詳細画面で、Start your free trial now をクリックします。

続けて、最初のスタックのデプロイリージョンを選択します。今回は日本(Japan)を選び、「Finish Setup」で作成しました。

スタック作成後の「Welcome to Grafana Cloud」画面では用途別のクイックスタートが並びますが、今回は個別の連携を自分で設定したかったので「Skip setup」を選びます。

「もう慣れているならセットアップをスキップしてよい」という確認モーダルが出るので、「See all data connections」を選んで連携一覧に進みます。

余談:Grafana CloudのAIアシスタント「Grot」にも聞いてみた
連携方法を探している途中、画面から呼び出せる Grafana Cloud の AI アシスタント「Grot」というものもあったので、「OTLPのエンドポイントとトークンはどうやって取得するの?」と聞いてみました。

回答はおおむね「Grafana Cloud Portal にサインイン → 組織の Overview からスタックを選択 → OpenTelemetry タイルの Configure をクリック」という趣旨で、後述する手順とも一致していました。
Claude Code向けトークンの発行
Add new connections 画面で「OpenTelemetry」と検索すると「Claude Code」のタイルが出てくるので、クリックします。

「Configuration details」タブで設定していきます。Access Policy token name に任意の文字列(今回は claude-code としました)を入力します。API キーが発行できるので、コピーしておきます。このトークンは 1 回しか表示されません。私は 1Password に保管しました。ちなみにこの生 API キーはこの後の設定では使いません。

発行した Access Policy は、Grafana Cloud の管理画面(Access Policies)からも確認できます。

続く「2. Configure Claude Code」では「Config File (Recommended)」タブに切り替えることで、~/.claude/settings.json にそのまま貼れる JSON が表示されるのでコピーしておきます。これが重要です。私は 1Password に保管しました。「3. Install Dashboards」の Install ボタンからは、Claude Code 専用の事前構築済みダッシュボードもまとめてインストールできました。

Config File タブに表示される JSON は、以下のような形でした。
{
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://otlp-gateway-prod-ap-northeast-0.grafana.net/otlp",
"OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Basic <長いBase64文字列>",
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "cumulative"
}
}
このうち OTEL_EXPORTER_OTLP_HEADERS だけは平文でトークンを持つ箇所なので、settings.json には含めず、代わりに otelHeadersHelper で置き換える方針にします。
ヘルパースクリプトと settings.json の設定
pass は秘密情報を 1 件ずつ GPG で暗号化して保存する仕組みで、この 1 件分のデータを「エントリ」と呼びます。エントリ名はそのデータを指す名前(パスのようなもの)で、pass show <エントリ名> として指定すると値を取り出せます。が、このコマンドをそのまま実行するとコマンドログにトークン文字列がそのまま出てしまうので気を付けてください。
今回は claude-code/grafana-otlp-token というエントリ名を付け、ここに Grafana Cloud OTLP エンドポイント用の Basic 認証情報を保存します。保存する値は「Grafana の API トークン単体」ではなく、Grafana の「Configure Claude Code」画面(Config File タブ)の ~/.claude/settings.json に表示される OTEL_EXPORTER_OTLP_HEADERS の Basic より後ろの文字列、つまり Instance ID と API トークンを : で連結し、base64 でエンコードしたもの である点に注意してください。
スクリプト側では一切加工せず、この文字列をそのまま保存・利用します。

以下コマンドで Grafana Cloud OTLP エンドポイント用の Basic 認証情報を保存します。
pass insert claude-code/grafana-otlp-token
実行するとパスワード入力と同様に値の入力を求められるので、コピーしておいた文字列を貼り付けます。確認のため2回入力します。
Enter password for claude-code/grafana-otlp-token:
Retype password for claude-code/grafana-otlp-token:
保存できたら、エントリが存在することを確認します。
pass ls claude-code
実行結果例
emiki@<hostname>:~/work$ pass ls claude-code
claude-code
└── grafana-otlp-token
emiki@<hostname>:~/work$
余談:トークンをうっかり直貼りしてしまった話
pass insert で値を登録する際、一度チャット画面に Basic 認証用の文字列(Instance ID + API トークンを base64 エンコードしたもの)をそのまま貼ってしまい、慌てて Grafana Cloud 側の Access Policy トークンを Revoke(無効化)して作り直す一幕がありました。
ちなみに、トークンを作り直す場合はこの画面(Connections > Integrations > Claude Code の「Create token」ボタン)から再作成できます。なお、この画面はデータを可視化する Grafana ダッシュボードではなく、Grafana Cloud ポータルの連携設定(Integrations)ページです。

# 新しいトークンで pass の中身を強制上書き(-f は force)
pass insert -f claude-code/grafana-otlp-token
トークンは「1 回しか表示されない」性質上、貼り間違いに気づいたら再発行が一番早い対処です。認証系のトークンを扱う作業では、ターミナルへの貼り付け先を都度確認する癖をつけておくと安心です。
続けてヘルパースクリプトを作成します。スクリプトを格納するディレクトリを作成します。
mkdir -p ~/.claude/scripts
私は vim で otel-headers.sh と言う名前のファイルを作成しました。スクリプトの中身は以下です。
#!/usr/bin/env bash
# Grafana Cloud OTLP の認証ヘッダーを pass から動的取得して JSON で出力する
set -euo pipefail
TOKEN="$(pass show claude-code/grafana-otlp-token 2>/dev/null || true)"
if [ -z "${TOKEN}" ]; then
echo '{"error":"grafana-otlp-token not found in pass"}' >&2
exit 1
fi
printf '{"Authorization":"Basic %s"}\n' "${TOKEN}"
Claude Code はこのスクリプトを直接実行するため、実行権限を付けておきます。
chmod +x ~/.claude/scripts/otel-headers.sh
settings.json には、Grafana が提示した JSON の env から OTEL_EXPORTER_OTLP_HEADERS だけを除いて反映し、代わりに otelHeadersHelper を追加しました。
{
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://otlp-gateway-prod-ap-northeast-0.grafana.net/otlp",
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "cumulative"
},
// ...(model、enabledPlugins、extraKnownMarketplaces、theme など、本記事の趣旨とは関係のない設定は省略)
"otelHeadersHelper": "/home/emiki/.claude/scripts/otel-headers.sh"
}
続けて、settings.json に構文エラーがないこと、otelHeadersHelper のパスが絶対パスかつ実在し、実行権限が付いていることをコマンドで確認します。
以下のコマンドで、JSON 構文が正しいかを確認します。jq . の . は「そのまま出力する」という意味のフィルタで、変換はしませんが必ずパース(解析)は行うため、JSONが壊れていれば jq がエラーで終了します。パース結果自体は使わないので > /dev/null で捨て、&& で「jq が成功したときだけ」echo を実行させています。
jq . ~/.claude/settings.json > /dev/null && echo "JSON構文: OK"
実行結果例
emiki@<hostname>:~/work$ jq . ~/.claude/settings.json > /dev/null && echo "JSON構文: OK"
JSON構文: OK
emiki@<hostname>:~/work$
以下のコマンドで、otelHeadersHelper の値を変数に取り出します。jq -r '.otelHeadersHelper' で JSON のトップレベルにある otelHeadersHelper の値だけを取り出します。-r(raw output) を付けることで、値がダブルクォート付きではなくプレーンな文字列で出力されます。これを $( ) で HELPER というシェル変数に代入しているだけなので、画面には何も表示されません。以降のコマンドはこの $HELPER を使い回します。
HELPER=$(jq -r '.otelHeadersHelper' ~/.claude/settings.json)
実行結果例
emiki@<hostname>:~/work$ HELPER=$(jq -r '.otelHeadersHelper' ~/.claude/settings.json)
emiki@<hostname>:~/work$
以下のコマンドで、そのパスが絶対パスであり、かつ実在するかを確認します。[[ "$HELPER" = /* ]] は bash の拡張テスト構文で、$HELPER が / から始まる文字列かどうかをパターンマッチで判定し、絶対パスかを確認します。[ -e "$HELPER" ] はそのパスに何らかのファイルが実在するかを確認する test コマンドです。&& で繋いでいるので、両方を満たしたときだけ OK が出ます。
[[ "$HELPER" = /* ]] && [ -e "$HELPER" ] && echo "絶対パス・実在: OK"
実行結果例
emiki@<hostname>:~/work$ [[ "$HELPER" = /* ]] && [ -e "$HELPER" ] && echo "絶対パス・実在: OK"
絶対パス・実在: OK
emiki@<hostname>:~/work$
以下のコマンドで、実行権限が付いているかを確認します。-x は実行権限ビットが付いているかを判定する test オプションで、chmod +x した対象であればここで真になります。
[ -x "$HELPER" ] && echo "実行権限: OK"
実行結果例
emiki@<hostname>:~/work$ [ -x "$HELPER" ] && echo "実行権限: OK"
実行権限: OK
emiki@<hostname>:~/work$
いずれも && で連結しているため、各チェックが実際に成功したときだけ OK が出力されます。たとえば JSON に構文エラーがあれば jq が失敗して JSON構文: OK は表示されず、ヘルパースクリプトが存在しなければ [ -e ] や [ -x ] が失敗して以降の OK も出ません。単に文字列を出しているのではなく、条件を満たした場合のみ出力される点がポイントです。
動作確認
スクリプト単体の実行確認
ヘルパースクリプト単体を実行し、JSON が返ってくることを確認します。jq -r 'keys' でヘッダー名(キー)の一覧だけを取り出しており、値(トークンの中身)は出力しません。ここでトークンの値までうっかり表示してしまわないよう、キー名だけを確認する形にしています。
~/.claude/scripts/otel-headers.sh | jq -r 'keys'
実行結果例
emiki@<hostname>:~/work$ ~/.claude/scripts/otel-headers.sh | jq -r 'keys'
[
"Authorization"
]
emiki@<hostname>:~/work$
Claude Code 実行時のデバッグログ確認
Claude Code を実際に 1 回実行し、OTLP 送信時の挙動をデバッグログに記録します。
claude -p "say hi" --debug-file /tmp/claude-debug.log
実行結果例
emiki@<hostname>:~/work$ claude -p "say hi" --debug-file /tmp/claude-debug.log
Ignoring 10 permissions.allow entries from .claude/settings.local.json: this workspace has not been trusted. Run Claude Code interactively here once and accept the trust dialog, or set projects["<作業ディレクトリのパス>"].hasTrustDialogAccepted: true in /home/emiki/.claude.json.
Hi! What can I help you with today?
emiki@<hostname>:~/work$
Ignoring ... permissions.allow entries ... は、このディレクトリをまだ信頼(Trust)していないために出る警告で、OTel送信の成否とは無関係です。対話モードで一度起動してTrustダイアログを承認すれば消えます。
デバッグログの中にOTel認証エラー(OTEL diag error・Unauthorized・401)が出ていないかを確認します。
grep -i -E "OTEL diag error|Unauthorized|401" /tmp/claude-debug.log
実行結果例
emiki@<hostname>:~/work$ grep -i -E "OTEL diag error|Unauthorized|401" /tmp/claude-debug.log
emiki@<hostname>:~/work$
OTLP エンドポイントへの直接疎通確認
Claude Code を介さず、Grafana Cloud の OTLP エンドポイントへ直接リクエストを送り、認証情報単体で疎通できるかを確認します。HTTP 200 が返ってくれば OK です。
curl -s -o /dev/null -w "HTTP %{http_code}\n" -X POST \
-H "Authorization: Basic $(pass show claude-code/grafana-otlp-token)" \
-H "Content-Type: application/x-protobuf" \
--data-binary "" \
"https://otlp-gateway-prod-ap-northeast-0.grafana.net/otlp/v1/metrics"
実行結果例
emiki@<hostname>:~/work$ curl -s -o /dev/null -w "HTTP %{http_code}\n" -X POST \
-H "Authorization: Basic $(pass show claude-code/grafana-otlp-token)" \
-H "Content-Type: application/x-protobuf" \
--data-binary "" \
"https://otlp-gateway-prod-ap-northeast-0.grafana.net/otlp/v1/metrics"
HTTP 200
emiki@<hostname>:~/work$
Grafana Cloud での送信確認
Grafana Cloud にログが送信されているか確認します。
Grafana Cloud のマイページの Grafana Cloud Portal で、作成した stack のリンクを開きます。

左メニューの「Explore」から確認します。

データソースをログ用の Loki(-logs のインスタンス)に切り替え、

service_name = claude-code と指定します。

すると、Logs volume にデータが表示され始めました。

何度かテスト実行を繰り返すと、時系列でログの件数が積み上がっていくのも確認できました。

claude_code.api_request や claude_code.assistant_response といったイベントが時系列で並んでおり、送信成功を確認できました。
Claude に指示する際の注意事項
Sonnet 5 で送信不可調査を行ったのですが、以下のように指示しないとトークンを平文で取得しようとしました。
Grafana Cloud への OTLP 送信ができない原因を調査してほしい。
ただし以下のルールを厳守すること:
【セキュリティルール】
- pass show や curl の -H の中身など、認証トークン(Basic の後ろの文字列や glc_ で始まる値)を
ターミナルやチャットに絶対に出力しないこと
- トークンをデコード・base64 decode してチャットに貼ることも禁止
- 調査に必要なのは「成否(HTTP ステータス)」と「文字列長・先頭数文字」程度の間接情報のみ
【調査してほしいこと】
1. ~/.claude/settings.json の構文と otelHeadersHelper のパス・実行権限
2. ヘルパースクリプトが JSON を正しく返すか(キー名だけ確認、値は伏せる)
3. pass に保存された値が「空でない」ことと「文字列長」だけ確認(中身は出さない)
4. OTLP エンドポイントへの疎通確認を curl で行い、HTTP ステータスだけ報告
(Authorization ヘッダーの中身は echo せず、$(pass show ...) を直接埋め込む形にする)
5. 401 が出る場合、pass の値が古いトークンのままになっている可能性が高いので指摘してほしい
まず調査計画を提示してから、コマンドを1つずつ実行して。
おわりに
otelHeadersHelper を使って Claude Code の OTLP 認証トークンを WSL 上の pass に保管し、Grafana Cloud への送信を確認するところまで到達しました。
otelHeadersHelper の実装自体に問題はなかったのですが「Grafana Cloud の Basic 認証が base64("<Instance ID>:<APIトークン>") という組み合わせ形式を要求する」という仕様を理解しないまま、Grafana 画面からコピーする値を「トークン単体」だと思い込んでいたために、ログの送信がうまくいかないシーンがありました。
途中、Claude Code 自身にデバッグログの解析と原因調査を依頼しましたが、認証トークンの中身をうっかり出力させてしまうリスクがあったため、「トークンの中身は絶対に出力・デコードしない」というセキュリティルールを明示した上で調査を任せる必要がありました。それでも文字列長やHTTPステータスといった間接的な情報だけで、curl による疎通確認まで一気に進めてくれました。困ったときに「原因を調べて直して」と頼めるのは、AI エージェントで作業する際の心強いポイントですが、機密情報を扱う調査を任せる際は、こうしたガードレールを先に敷いておくことが欠かせないと感じます。
同じように Claude Code のテレメトリを Grafana Cloud に送ろうとして 401 エラーにハマっている方の参考になれば幸いです。
本記事への質問やご要望については画面下部の「DevelopersIOへのご意見」からお問い合わせいただけます。
参考





