02 — trace_id 的输出位置¶
trace_id 进入 LiteLLM 后会同时往 6 个出口 流,分别由不同模块负责。本篇逐个讲清楚:是什么、在哪、怎么查。
出口一览¶
| # | 出口 | 链路 | 写入位置 | 检索方式 |
|---|---|---|---|---|
| ① | HTTP 响应头 | A | AccessLogMiddleware |
curl 看响应头 |
| ② | access log 文件 | A | 自定义 logger | grep 容器内 access.log |
| ③ | 应用 JSON 日志 | A | litellm/_logging.py |
ELK 按字段检索 |
| ④ | SpendLogs.session_id |
B | _get_session_id_for_spend_log |
pgsql SQL 查询 |
| ⑤ | S3 日志正文 trace_id 字段 |
B | s3_v2.py JSON 写入 |
S3 下载文件 grep |
| ⑥ | UI Logs 页 Session ID 列 | B | UI 拉取 SpendLogs |
Filter → Session ID |
链路 A、B 概念见 README.md。
① 响应头¶
字段: x-litellm-trace-id 与 x-trace-id 同时回写(同一个值)。
写入位置: access_log_middleware.py:58-60
查看方式:
curl -i -X POST https://litellm.example.com/v1/chat/completions \
-H "Authorization: Bearer sk-..." \
-H "X-Trace-Id: T-VERIFY-001" \
... 2>&1 | grep -i trace-id
注意: 响应头来自链路 A,可能与 SpendLogs.session_id(链路 B)不一致,详见 01-input-channels.md。
② access log 文件¶
每条 HTTP 请求结束后写一行 NCSA-like 格式日志。当前实现里 access log 行没有显式打 trace_id 字段——而是依赖应用 JSON 日志承载(出口 ③)。如果需要按 trace_id grep access log,请改成 ELK 走 ③ 的 JSON 字段。
写入位置: access_log_middleware.py:82-91
文件路径与切割策略: 见 docs/operations/logging-files.md。
③ 应用 JSON 日志(含 trace_id 字段)¶
LiteLLM 业务日志(business log)通过自定义 JSON formatter 输出,每条日志会从 ContextVar 取出当前请求的 trace_id 并注入 JSON。
写入位置: litellm/_logging.py:166-168
trace_id = get_request_trace_id()
if trace_id is not None and "trace_id" not in json_record:
json_record["trace_id"] = trace_id
ContextVar 实现: litellm/proxy/request_context.py,由 AccessLogMiddleware.dispatch 在请求进入时 set、请求结束时 reset。
启用条件: 环境变量 JSON_LOGS=true(或 config yaml 里 json_logs: true)。
ELK 检索示例:
典型一行日志:
{
"timestamp": "2026-04-30T10:13:06.123Z",
"level": "INFO",
"message": "Request received",
"trace_id": "SPAN-2026-04-30-abc123",
"...": "..."
}
④ LiteLLM_SpendLogs.session_id(pgsql)¶
每次 LLM 请求结束后会写一行 SpendLogs,session_id 列即 trace_id。
写入位置: spend_tracking_utils.py:499-522
def _get_session_id_for_spend_log(kwargs, standard_logging_payload) -> str:
if standard_logging_payload is not None and standard_logging_payload.get("trace_id") is not None:
return str(standard_logging_payload.get("trace_id"))
if kwargs.get("litellm_trace_id") is not None:
return str(kwargs.get("litellm_trace_id"))
return str(uuid.uuid4())
SQL 速查(精确匹配):
SELECT request_id, session_id, model, "startTime", spend
FROM "LiteLLM_SpendLogs"
WHERE session_id = 'SPAN-2026-04-30-abc123'
ORDER BY "startTime" DESC;
SQL 速查(前缀匹配,与 UI 行为一致):
SELECT request_id, session_id, model, "startTime", spend
FROM "LiteLLM_SpendLogs"
WHERE session_id LIKE 'SPAN-2026-04-30-%'
ORDER BY "startTime" DESC;
关联其他列:
metadata->>'trace_id':链路 B 也会把同一 trace_id 写入metadataJSON(来自metadata_from_headers["trace_id"],但实际持久化路径取决于 metadata 合并逻辑,不保证总是有,建议直接以session_id列为准)。request_id:单次请求 ID,与 trace_id 是 N:1 关系(一个 trace 可能含多条 request)。
⑤ S3 日志正文 trace_id 字段¶
如果启用了 S3 日志(s3_v2 callback),每条请求会向 S3 写一份完整 JSON(含 messages、response、cache 信息等)。文件正文里 trace_id 字段即同一个 trace_id。
写入字段: StandardLoggingPayload.trace_id(与 SpendLogs.session_id 同源)。
S3 路径构造: 由 s3_path_components 配置决定,详见 docs/billing-and-pricing/06-s3-cost-map.md 与同名集成代码 litellm/integrations/s3_v2.py。
典型用法:
- 从 UI Logs 页或 pgsql 拿到
request_id - 按
s3_path_components模板拼出 S3 key(含request_id) - 下载 JSON 文件
grep '"trace_id"' xxx.json或直接 jq 取值
待办:把
s3_object_key写回SpendLogs.metadata,UI 上一键复制 S3 路径,目前未实现。
⑥ UI Logs 页 Session ID 列¶
LiteLLM Dashboard /ui/logs 页面的"Session ID"列即 SpendLogs.session_id。
两种用法:
- 行内点击:单元格可点击,触发抽屉的 session 模式(拉取
/spend/logs/session/ui?session_id=精确值,把同一 session 的所有 request 行平铺到右侧抽屉)。 - 过滤检索:高级 Filter → Session ID 输入框,走
/spend/logs/ui?session_id=前缀LIKE 匹配。
出口对照:上游 → 下游链路¶
flowchart LR
IN[trace_id 进入] --> A[ContextVar]
IN --> B["data['litellm_trace_id']"]
A --> O1[① 响应头]
A --> O3[③ 应用 JSON 日志]
%% access log 当前不打 trace_id 字段,省略边
B --> S[StandardLoggingPayload.trace_id]
S --> O4[④ SpendLogs.session_id]
S --> O5[⑤ S3 日志正文 trace_id]
O4 --> O6[⑥ UI Logs Session ID]
只读这一张图:
- ContextVar(链路 A)→ 响应头 + 应用 JSON 日志
data["litellm_trace_id"](链路 B)→ SpendLogs / S3 / UI
显式传 header 时两条链路同步;不传时两条链路各跑各的 UUID。