shunt 可以通过 OTLP/HTTP 将 trace、指标和日志 导出到你自己的 OpenTelemetry Collector(或任何 OTLP 兼容后端)。它是可选启用、默认关闭的 —— 没有 [otel] 段时,任何数据都不会离开本机 —— 并且与 Sentry 相互独立,你可以只启用其一或两者都启用。
本页讲的是 shunt 自身向外发送的遥测。网关独立的遥测接收处理相反的方向 —— 托管 Claude Code 客户端发送给 shunt 的 OTLP 载荷,verbatim 中继到 [server.gateway.telemetry] 中的目标。两者独立配置,可以指向不同的 collector。
启用
一个键即可启用 —— 指向你的 collector 的 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 # (默认) 基于 head 的 trace 采样,0.0–1.0
traces = true # (默认) 导出请求 span
metrics = true # (默认) 导出用量指标
logs = true # (默认) 导出日志事件(stderr 日志不受影响)
include_session_id = false # (默认) 将客户端 session id 排除在 span 之外
[otel.headers] # 可选:每次请求附带的 header,例如托管 collector 的令牌
authorization = "Bearer <token>"设置 endpoint = ""(例如 SHUNT_OTEL__ENDPOINT="")可在不删除该段的情况下再次关闭导出。无效的 endpoint、非 http(s) 的 URL、或超出范围的 sample_ratio 都是启动错误,因此一个拼写错误不会悄无声息地丢弃所有导出。
三种信号
| 信号 | 导出内容 | 说明 |
|---|---|---|
| Trace | 每次请求的 proxy_request span |
通过 sample_ratio 进行 head 采样。低基数;不含请求/响应正文。 |
| 指标 | 下方列出的低基数序列 | 与 [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 |
流式响应为到 header 的延迟;其他响应为完整延迟。提交式流式响应因 header 在上游响应前提交,测量从分发到尝试分类的时间。 |
shunt.ttft |
直方图(ms) | provider, model, client_kind, managed |
从请求开始到第一个完整的非保活 SSE 帧的时间。 |
shunt.stream_outcome |
计数器 | provider, model, outcome, client_kind, managed |
每个 SSE 记录一个最终结果:completed、error_event、upstream_cut 或 client_disconnect。 |
shunt.tokens |
计数器 | provider, model, kind, client_kind, managed |
最后报告的流式 token 用量(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 分析事件;payload 和属性会被丢弃。 |
shunt.gateway_telemetry_ingest |
计数器 | signal, outcome |
按接收结果统计入站网关 OTLP 载荷(metrics/logs/traces):relayed;没有任何目标 opt-in 时为 discarded;在途中继上限饱和时为 shed;bearer 无效或 body 超限时为 rejected。 |
shunt.upstream_retries |
计数器 | provider, reason |
有次数限制的临时上游重试。 |
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 |
阶段路由器的档位决策,按路由器的模型 id(匹配后的形式,[1m] 提示已剥离)、选中的档位(capable/efficient)以及选中的原因计数。不含 count_tokens 探测。 |
shunt.router.judge_calls |
计数器 | model, algorithm, outcome |
配置了裁判的 [models.router] 条目发起的裁判调用,按路由器的模型 id、配置的 type 以及该次调用的结果计数:decided,或 timeout、oversized、upstream_error、invalid_reply、translation_failed、budget_exhausted 之一。budget_exhausted 表示会话已用完 max_judge_calls,调用被拒绝。上游中途截断的回复计为 upstream_error,而非 invalid_reply。在阶段路由器上,后几种结果都会把该轮路由到 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 |
把会话从已固定档位移开的决策。升档刻意比降档容易,因此两个方向分开计数。 |
shunt.requests_shed |
计数器 | — | 在 max_concurrent_requests 上限处被拒绝的入站请求。拒绝本身仅以 debug! 记录,因此这个计数器才是网关饱和的可见信号。 |
shunt.pool.quota_utilization |
仪表 | provider, window |
5h、7d 或 7d_oi 窗口中已启用、已观测且未过期的 quota 值的最小使用率。 |
shunt.pool.rotations |
计数器 | provider, reason |
离开账户的切换次数以及 pool 耗尽的请求数。 |
shunt.pool.reprobes |
计数器 | provider |
在陈旧近配额 Codex/ChatGPT 账户的首次 HTTP 发送时提交的重新探测;启用 WebSocket 的提供方只统计 inbound HTTP 探测。 |
shunt.store.write_failures |
计数器 | subsystem |
对可选 [store] 的写入失败,按 subsystem 统计:pool(write-behind 刷新)、spend 或 gateway(token 端点)。失败的池刷新会在下一个 15 秒周期重试,但若本进程丢失了存储 hold,池刷新会停止。失败的支出写入会报告给管理端调用方,失败的 token 端点写入会报告给客户端;后台网关刷新失败没有调用方,只记录日志。 |
shunt.upstream.status |
仪表 | provider |
观测到的 [server.status] Statuspage severity:0 none、1 minor、2 major、3 critical。当前处于 unknown(无信号)状态的 provider 不会以 0 上报,而是从该序列中完全省略。 |
这五个按请求统计的序列会额外携带两个属性。client_kind 从请求自身的客户端 header(x-app、originator、user-agent)读取,取值为 claude-code、codex 或 other;原始 header 值绝不会被导出。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"。同样的两个值也会以 shunt.client_kind 和 shunt.managed 记录在请求 span 上。入站 Codex WebSocket 轮次没有请求 span,因此它的归属信息只出现在指标上。
隐私
shunt 在指标和 trace 中从不导出请求/响应正文、header 或凭据。
- 指标和 trace 保持低基数且不含正文。在 OTLP trace 导出中,请求 span 的客户端 session id 仅在
include_session_id = true(默认关闭)时、且仅在 trace 导出处于启用状态时才发送给 collector。同样的规则也适用于 Sentry 的 trace 导出([sentry] traces_sample_rate/include_session_id)。当没有任何 span 导出处于启用状态时,该 id 仍会像以前一样只保留在本地请求 span 上。 - 日志 会如实反映 shunt 自身的诊断事件,因此与 stderr 日志一样,可能包含源自请求的字段(上游错误正文、已认证的客户端 id)。若需要严格不含正文的导出,请将
logs = false,仅保留指标/trace。
导出的 resource 公布 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] 配置参考。