shunt は トレース・メトリクス・ログ を OTLP/HTTP で自分の OpenTelemetry Collector(または OTLP 互換バックエンド)へエクスポートできます。オプトインで、デフォルトはオフ です — [otel] セクションがなければ、何もマシンの外に出ません — そして Sentry とは独立して動作するため、どちらか一方でも両方でも有効にできます。
このページは、外へ出ていく shunt 自身の テレメトリーについてのものです。ゲートウェイの独立したテレメトリー受信は逆方向 — 管理された Claude Code クライアント が shunt へ送る OTLP ペイロード — を扱い、[server.gateway.telemetry] の宛先へ verbatim に中継します。両者は独立して設定され、異なるコレクターを指すこともできます。
有効化
キー 1 つで有効になります。コレクターの OTLP/HTTP レシーバーを指定してください:
[otel]
endpoint = "http://localhost:4318" # OTLP/HTTP のベース URL。shunt が /v1/{traces,metrics,logs} を付加それ以外はすべて妥当なデフォルトを持ちます:
[otel]
endpoint = "http://localhost:4318"
service_name = "shunt" # (デフォルト) service.name リソース属性
environment = "prod" # 任意: deployment.environment.name
sample_ratio = 1.0 # (デフォルト) ヘッドベースのトレースサンプリング、0.0–1.0
traces = true # (デフォルト) リクエストスパンをエクスポート
metrics = true # (デフォルト) 使用量メトリクスをエクスポート
logs = true # (デフォルト) ログイベントをエクスポート(stderr ログには影響なし)
include_session_id = false # (デフォルト) クライアントのセッション id をスパンから除外
[otel.headers] # 任意: リクエストごとのヘッダー(例: ホスト型コレクターのトークン)
authorization = "Bearer <token>"endpoint = ""(例: SHUNT_OTEL__ENDPOINT="")にすると、セクションを削除せずにエクスポートを再び無効化できます。無効なエンドポイント、http(s) 以外の URL、範囲外の sample_ratio は 起動エラー になるため、タイプミスですべてのエクスポートが黙って失われることはありません。
3 つのシグナル
| シグナル | エクスポートされるもの | 備考 |
|---|---|---|
| トレース | リクエストごとの proxy_request スパン |
sample_ratio によるヘッドベースサンプリング。低カーディナリティで、リクエスト/レスポンス本文は含まない。 |
| メトリクス | 以下に示す低カーディナリティの系列 | [sentry] metrics = true のときに shunt が Sentry へ送るものと同じ系列。 |
| ログ | shunt の tracing ログイベントを OTLP にブリッジ |
stderr ログには影響しない。 |
各シグナルは traces / metrics / logs で個別にオン/オフできます。
メトリクス系列
| 系列 | 種類 | 属性 | 意味 |
|---|---|---|---|
shunt.requests |
カウンター | provider, model, http.response.status_code, caller, client_kind, managed |
プロキシされた推論リクエスト。caller はインバウンドのトラフィックなら client、[models.router] エントリが呼び出し元に代わって行ったジャッジ呼び出しなら router。 |
shunt.latency |
ヒストグラム(ms) | provider, model, http.response.status_code, caller, client_kind, managed |
ストリームではヘッダーまで、それ以外では応答全体のレイテンシ。コミット型ストリーミング応答はヘッダーがアップストリーム応答より先にコミットされるため、ディスパッチから試行分類までを測定します。 |
shunt.ttft |
ヒストグラム(ms) | provider, model, client_kind, managed |
リクエスト開始から最初の完全な非キープアライブ SSE フレームまでの時間。 |
shunt.stream_outcome |
カウンター | provider, model, outcome, client_kind, managed |
SSE の最終結果を 1 件記録: completed, error_event, upstream_cut, client_disconnect。 |
shunt.tokens |
カウンター | provider, model, kind, client_kind, managed |
最後に報告されたストリーミング token usage (input, output, cache_read, cache_creation)。非ストリーミングは記録しない。 |
shunt.codex_continuation |
カウンター | provider, outcome |
Codex WebSocket continuation の hit または fallback。 |
shunt.codex_ws_overflow |
カウンター | provider, outcome |
並行 turn が専用 overflow 接続を開いた場合は opened、接続上限で拒否された場合は refused(issue #248)。 |
shunt.codex_client_events |
カウンター | event |
サニタイズ済みイベント名ごとの Codex CLI analytics イベント。payload とプロパティは破棄される。 |
shunt.gateway_telemetry_ingest |
カウンター | signal, outcome |
インバウンドのゲートウェイ OTLP ペイロード(metrics/logs/traces)を受信結果別に集計: relayed、どの宛先も opt-in していなければ discarded、飛行中の中継上限で飽和していれば shed、不正なベアラーや上限超過ボディは rejected。 |
shunt.upstream_retries |
カウンター | provider, reason |
回数制限付きの一時的な upstream retry。 |
shunt.failover |
カウンター | provider, state |
順序付きアップストリームのフェイルオーバー遷移: attempted, advanced, exhausted。 |
shunt.router.decisions |
カウンター | model, algorithm, target, source |
すべての [models.router] type のルーティング決定と、[models.subagents] オーバーレイの決定(algorithm = "subagents")。決定を下した [[models]] エントリの id(マッチした形なので [1m] ヒントは除去済み)、設定された type、決定が解決したモデル id、選んだ理由ごとに数える。count_tokens のプローブは除外。 |
shunt.stage_router.decisions |
カウンター | model, tier, source |
ステージルーターのティア決定。ルーターの model id(マッチした形なので [1m] ヒントは除去済み)、選ばれたティア(capable/efficient)、選んだ理由ごとに数える。count_tokens のプローブは除外。 |
shunt.router.judge_calls |
カウンター | model, algorithm, outcome |
ジャッジを設定した [models.router] エントリが行ったジャッジ呼び出し。ルーターの model id、設定された type、呼び出しがどう決着したか(decided、または timeout、oversized、upstream_error、invalid_reply、translation_failed、budget_exhausted のいずれか)ごとに数える。budget_exhausted はセッションが max_judge_calls を使い切ったため呼び出しが拒否されたことを表す。アップストリームが途中で打ち切った応答は invalid_reply ではなく upstream_error として数える。ステージルーターではこれらの結果はいずれもそのターンを picker の既定値にルーティングし、llm_classifier や composite のエントリ、classifier 形式の [models.subagents] オーバーレイではアルゴリズム自身のフォールバックがターンを閉じる。呼び出しなしで保持された割り当てから応答したターンは retained として記録される。escalation や advisor のエントリでは、完成したターンのあとの失敗は留め置いたそのターンを代わりに返し(fail_open = false の advisor はリクエストを失敗させ、失敗したレビューはやはり upstream_error や timeout といったその結果として数え、max_judge_calls で拒否されたレビューは budget_exhausted として数える)、advisor のレビューは algorithm = "advisor" として数える。留め置いたターン自体はジャッジ呼び出しではない。 |
shunt.stage_router.flips |
カウンター | model, from, to |
セッションを固定済みのティアから動かした決定。昇格は容易に、降格は難しくという設計なので、2 つの方向を分けて数える。 |
shunt.requests_shed |
カウンター | — | max_concurrent_requests の上限で拒否されたインバウンドリクエスト。拒否自体は debug! でのみログ出力されるため、ゲートウェイの飽和を可視化するのはこのカウンターです。 |
shunt.pool.quota_utilization |
ゲージ | provider, window |
5h, 7d, 7d_oi ごとに、有効で観測済みかつ期限切れでない quota 値の最小 utilization。 |
shunt.pool.rotations |
カウンター | provider, reason |
アカウントからの切り替えと pool exhaustion の回数。 |
shunt.pool.reprobes |
カウンター | provider |
陳腐化した近接クォータの Codex/ChatGPT アカウントで、最初の HTTP 送信時にコミットされた再プローブ。WebSocket 有効プロバイダーは inbound HTTP のみを数える。 |
shunt.store.write_failures |
カウンター | subsystem |
オプションの [store] への書き込み失敗を subsystem ごとに集計: pool(write-behind フラッシュ)、spend、gateway(トークンエンドポイント)。失敗したプールのフラッシュは次の 15 秒周期で再試行されるが、このプロセスがストアの hold を失うとプールのフラッシュは停止する。失敗した支出の書き込みは管理者の呼び出し元に、失敗したトークンエンドポイントの書き込みはクライアントに報告され、バックグラウンドのゲートウェイのフラッシュ失敗は呼び出し元がないためログにのみ記録される。 |
shunt.upstream.status |
ゲージ | provider |
観測された [server.status] Statuspage severity: 0 none、1 minor、2 major、3 critical。現在 unknown(信号なし)の provider は 0 として報告されるのではなく、この系列から完全に除外される。 |
リクエスト単位の 5 系列には属性が 2 つ追加されます。client_kind はリクエスト自身のクライアントヘッダー(x-app、originator、user-agent)から読み取った claude-code、codex、other のいずれかで、生のヘッダー値がエクスポートされることはありません。managed は、ゲートウェイが発行した JWT で認証されたリクエストのときだけ true になります。その資格情報こそが、クライアントにプッシュされた OTEL_* 環境を受け取る資格を与えるものです。両者を合わせると、shunt がサーバー側で導出した系列と、ゲートウェイモードのクライアント自身が出力する claude_code.* を区別できるため、ダッシュボードは claude_code.* があるところではそれを、それ以外では shunt.*{managed="false"} を採用し、二重計上せずに集計できます。このフィルターは、管理下のクライアントの claude_code.* メトリクスをバックエンドが実際に受け取っていることが前提です。metrics を有効にした [server.gateway.telemetry].forward_to の宛先経由でリレーされる場合も、クライアントが直接エクスポートする場合も含みます。受け取っていない場合は、managed の値を問わず shunt.* 全体を読み取ります。ジャッジ呼び出し(caller="router")は元のターンの client_kind を引き継ぎますが、managed は常に "false" です。shunt 自身のトラフィックであり、どのクライアントのエクスポーターも報告しないためです。インバウンド Codex エンドポイントは [server.auth] の資格情報しか受け付けないため、そのリクエストも常に managed="false" です。同じ 2 つの値はリクエストスパンにも shunt.client_kind / shunt.managed として記録されます。インバウンド Codex WebSocket のターンにはリクエストスパンがないため、その属性はメトリクスにだけ現れます。
プライバシー
shunt は メトリクスとトレース においてリクエスト/レスポンス本文・ヘッダー・認証情報を一切エクスポートしません。
- メトリクスとトレース は低カーディナリティで本文を含みません。OTLP トレースエクスポートでは、リクエストスパンのクライアント セッション id は、
include_session_id = true(デフォルトはオフ)のときのみ、しかもトレースエクスポートが有効な間だけコレクターへ送られます。同じルールは Sentry のトレースエクスポート([sentry] traces_sample_rate/include_session_id)にも適用されます。スパンのエクスポートが一切有効でない場合、id は従来どおりローカルのリクエストスパンにだけ残ります。 - ログ は shunt 自身の診断イベントをそのまま反映するため、stderr ログと同様に、リクエスト由来のフィールド(アップストリームのエラー本文、認証済みクライアント id)を含みうる点に注意してください。厳密に本文なしでエクスポートしたい場合は
logs = falseにして、メトリクス/トレースだけを残します。
エクスポートされるリソースは service.*、telemetry.sdk.*、および environment が設定されている場合は deployment.environment.name を広告します — host や process detector は動かないため、マシンのホスト名は付与されません — これに標準の OTEL_RESOURCE_ATTRIBUTES で設定した値が加わります。
標準の OTEL_ 環境変数
endpointとservice_nameはこの設定から取得され、OTEL_EXPORTER_OTLP_ENDPOINT/OTEL_SERVICE_NAMEより 優先されます。- 標準の
OTEL_EXPORTER_OTLP_HEADERSとOTEL_RESOURCE_ATTRIBUTESは、[otel.headers]と組み込みのリソース属性の上に マージされます。
すべてのキーは [otel] 設定リファレンス を参照してください。