01 — trace_id 的输入入口¶
LiteLLM 在收到一个请求后,会依次尝试 4 种方式拿到 trace_id。第一个非空命中即生效,后面的 fallback 不再覆盖。
优先级总览¶
| 顺序 | 来源 | 实际去向 | 备注 |
|---|---|---|---|
| 1 | HTTP header x-litellm-trace-id |
链路 A + 链路 B | LiteLLM 上游原生支持的 header |
| 2 | HTTP header x-trace-id |
链路 A + 链路 B | 公司内部 mirror / model-adapter 规范,本仓库 fork 加入 |
| 3 | 请求 body 字段 litellm_trace_id |
链路 B | 上游原生路径;不进 access log |
| 4 | 自动生成 UUID | 链路 A 与链路 B 各生成各的 | 兜底;两条链路的 UUID 可能不同 |
链路 A、链路 B 的概念见 README.md。
1. HTTP header x-litellm-trace-id(最高优先级)¶
链路 A(access log)—— access_log_middleware.py:51-55:
trace_id = (
request.headers.get("x-litellm-trace-id")
or request.headers.get("x-trace-id")
or str(uuid4())
)
链路 B(SpendLogs)—— litellm_pre_call_utils.py:572-590:
trace_id_from_header = headers.get("x-litellm-trace-id") or headers.get("x-trace-id")
if trace_id_from_header:
metadata_from_headers["trace_id"] = trace_id_from_header
data["litellm_trace_id"] = trace_id_from_header
两条链路各自独立读 header,但读取顺序一致。
2. HTTP header x-trace-id(公司内部规范)¶
公司内部 mirror / model-adapter 服务按 Confluence 规范发的就是这个 header。本仓库 fork 在两个位置都加了对它的识别(见上面同两段代码)。
示例 curl:
curl -X POST https://litellm.example.com/v1/chat/completions \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-H "X-Trace-Id: SPAN-2026-04-30-abc123" \
-d '{"model": "gpt-4", "messages": [{"role": "user", "content": "hi"}]}'
响应头会返回(双名回写):
参见 access_log_middleware.py:58-60。
3. 请求 body 字段 litellm_trace_id¶
LiteLLM 上游官方支持的方式。把 trace_id 直接放在请求 JSON 里:
{
"model": "gpt-4",
"messages": [{"role": "user", "content": "hi"}],
"litellm_trace_id": "MY-TRACE-001"
}
特点:
- 只走链路 B:能写进
SpendLogs.session_id、S3 日志正文。 - 不进链路 A:
AccessLogMiddleware只看 header,所以 access log 里仍是自动 UUID。 - 适用于:客户端 SDK 不方便加 header、但能改 body 的场景。
实现位置:litellm_logging.py:349-351 直接接收 litellm_trace_id 关键字参数赋给 Logging.litellm_trace_id。
4. 自动 UUID(兜底)¶
什么 trace_id 都不传时,两条链路各生成各的 UUID:
| 链路 | UUID 生成位置 | 后果 |
|---|---|---|
| A | access_log_middleware.py:54 str(uuid4()) |
access log / 应用 JSON 日志 / 响应头 拿到 UUID-A |
| B | _get_session_id_for_spend_log spend_tracking_utils.py:522 或 litellm_logging.py:350 str(uuid.uuid4()) |
SpendLogs.session_id 拿到 UUID-B |
两个 UUID 互相独立,绝大多数情况不相同。 想在 access log 与 SpendLogs 之间对齐请求,必须显式传 trace_id(推荐 header)。
⚠️ 业务方排查时常陷入这个坑:"为什么 access log 里这条请求的 trace_id 在 UI 上搜不到?"——答:因为没传 header,UI 用的 session_id 是另一条 UUID。
一个请求里同时传了 x-litellm-trace-id 和 x-trace-id?¶
代码里两个分支是 or,x-litellm-trace-id 优先;x-trace-id 仅在前者缺失时生效。
如果两个值不同,链路 A 与链路 B 都会取 x-litellm-trace-id,x-trace-id 被丢弃——但响应头会按 trace_id 实际值(即 x-litellm-trace-id)覆盖回写到两个 header 名上,可能让接入方误以为对方支持了 x-trace-id 输入。
建议:业务方只发一个 header,按公司规范统一发 X-Trace-Id 即可。
关于响应头回写¶
无论用上面哪种方式拿到 trace_id(即使是兜底 UUID),AccessLogMiddleware 都会在响应里同时写两个 header:
参见 access_log_middleware.py:58-60。这让上游不论用哪个 header 名都能从响应头取回同一份 trace_id。
⚠️ 但响应头里的 trace_id 来自链路 A,可能与
SpendLogs.session_id(链路 B)不一致——前提是没显式传 trace_id 走兜底 UUID。