跳转至

04 — 验证与排错

本篇收录:(a) end-to-end 验证脚本怎么用;(b) 常见故障的定位 checklist;(c) 上次踩过的回归预警。


测试脚本:scripts/trace-id/test_trace_id_behavior.py

目的: 在不改代码的前提下,对 5 种 trace_id 输入路径做端到端验证(发请求 → 等 batch flush → 查 pgsql SpendLogs.session_id)。

用法:

# 默认(连本地 4000 端口)
python scripts/trace-id/test_trace_id_behavior.py

# 指定后端
python scripts/trace-id/test_trace_id_behavior.py --base-url http://localhost:4000

# 调长 batch flush 等待时间
python scripts/trace-id/test_trace_id_behavior.py --wait 20

# 换模型
python scripts/trace-id/test_trace_id_behavior.py --model claude-opus-4-7

前置依赖:

  • LITELLM_MASTER_KEY.env
  • DATABASE_URLLITELLM_LOCAL_PG_* 一组
  • pip install requests psycopg2-binary
  • 模型存在且支持 mock_response(默认 gpt-5.4

5 个场景:

场景 header / body 预期 session_id
A x-litellm-trace-id: HSH-A-... 等于 header 值
B (无) 自动 UUID
C x-trace-id: HSH-C-... 等于 header 值(本仓库 fork 新增)
D body litellm_trace_id: HSH-D-... 等于 body 值
E x-litellm-session-id: HSH-E-... 自动 UUID(这个 header 不识别,是负面用例)

跑通后输出示例:

✅ A_existing_header   expected='HSH-A-...'  actual='HSH-A-...'
✅ B_no_header         expected=None          actual='<uuid>'
✅ C_x_trace_id_header expected='HSH-C-...'  actual='HSH-C-...'
✅ D_body              expected='HSH-D-...'  actual='HSH-D-...'
❌ E_x_litellm_session_id expected='HSH-E-...' actual='<uuid>'   ← 期望失败

E 场景为什么是 ❌ 但符合预期: x-litellm-session-id 是社区上游某个分支引入的 header,本仓库未识别。脚本里保留它是为了将来上游若加上支持时能立刻发现。


SQL 速查

精确查一条:

SELECT request_id, session_id, model, "startTime", spend, status
FROM "LiteLLM_SpendLogs"
WHERE session_id = 'SPAN-2026-04-30-abc123'
ORDER BY "startTime" DESC;

前缀查一组(与 UI Filter 等价):

SELECT request_id, session_id, model, "startTime", spend, status
FROM "LiteLLM_SpendLogs"
WHERE session_id LIKE 'SPAN-2026-04-30-%'
  AND "startTime" >= NOW() - INTERVAL '24 hours'
ORDER BY "startTime" DESC
LIMIT 100;

统计某个 trace 下有多少个 span:

SELECT session_id, COUNT(*) AS span_count
FROM "LiteLLM_SpendLogs"
WHERE session_id LIKE 'SPAN-2026-04-30-%'
GROUP BY session_id
HAVING COUNT(*) > 1
ORDER BY span_count DESC;

对照 metadata:

SELECT session_id,
       metadata::jsonb -> 'trace_id'   AS metadata_trace_id,
       metadata::jsonb -> 'session_id' AS metadata_session_id
FROM "LiteLLM_SpendLogs"
WHERE session_id = 'SPAN-2026-04-30-abc123'
LIMIT 5;

参考脚本:test_trace_id_behavior.pyquery_session_id 函数。


故障定位 checklist

故障 1:access log 有 trace_id,但 SpendLogs.session_id 是 UUID

这是历史踩过的真实坑(已修复,但留作回归预警)。

症状:

  • 接入方在响应头能拿到 x-litellm-trace-id 是自己传的值
  • 但 UI / pgsql 里 SpendLogs.session_id 总是随机 UUID

根因: 链路 A 与链路 B 是两条独立通道(见 README.md)。早期版本 litellm_pre_call_utils.py 只把 header 写进 metadata_from_headers["trace_id"]没有data["litellm_trace_id"],导致 _get_session_id_for_spend_log 取不到值,fallback 到 uuid.uuid4()

修复点: 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   # ← 这行是关键

回归测试:test_trace_id_behavior.py 的 A、C 场景,必须 ✅。


故障 2:UI Filter 输入 Session ID 后没有结果

先排除显而易见的:

  1. 时间窗对吗?默认 24h,如果是几天前的请求要拉宽
  2. 大小写正确吗?LIKE 默认大小写敏感
  3. 是不是用了顶部那个白色搜索框而不是高级 Filter?见 03-ui-session-search.md

深层排查:

-- 看看库里到底有没有这条 session
SELECT COUNT(*) FROM "LiteLLM_SpendLogs"
WHERE session_id LIKE '<你的前缀>%';

如果 count = 0 但接入方坚称发了 trace_id:

  • 检查接入方 header 名拼写(区分 X-Trace-IdX-Trace-IDx-trace-id
  • 抓 nginx / paas 网关日志,看 header 是否被剥掉了
  • 看 LiteLLM 启动日志里 litellm_pre_call_utils.py 的 debug 输出(Extracted trace_id from header: ...

故障 3:响应头 x-trace-id 被覆盖

症状: 接入方传 X-Trace-Id: A,但 LiteLLM 响应头返回 x-trace-id: B,且 B 看起来像 UUID。

可能原因:

  • 上游 nginx / paas 把请求 header 剥掉了,LiteLLM 收到的是空 header → 自动生成 UUID
  • 同一个请求里 x-litellm-trace-idx-trace-id 都传了不同值,x-litellm-trace-id 优先生效(见 01-input-channels.md

验证: 在 LiteLLM 容器里 grep 应用日志,看 access log 中间件那一刻读到的 header 是什么。


故障 4:抽屉打开后只看到一行

症状: 点击 Session ID 单元格后抽屉只显示当前这一行,没看到该 session 的其他 request。

原因: 该 session 本身就只有一个 request。多 request 同 session 必须由接入方主动用同一个 trace_id 发多次请求。如果接入方不复用 trace_id(例如 model-adapter 的同一对话每次都生成新 UUID 当 trace),那么自然每行都是独立 session。

验证:

SELECT COUNT(*) FROM "LiteLLM_SpendLogs"
WHERE session_id = '<点击行的 session_id>';

如果 count = 1 那就是预期行为。


关键代码定位(一键跳转)

关注点 文件:行
ContextVar trace_id 定义 litellm/proxy/request_context.py:4
链路 A:读 header / 写响应头 litellm/proxy/middleware/access_log_middleware.py:51-60
链路 A:写应用 JSON 日志 litellm/_logging.py:166-168
链路 B:读 header / 写 data litellm/proxy/litellm_pre_call_utils.py:572-590
链路 B:Logging 类接收字段 litellm/litellm_core_utils/litellm_logging.py:349-351
链路 B:写 SpendLogs.session_id litellm/proxy/spend_tracking/spend_tracking_utils.py:499-522
后端 UI 接口(Session ID 过滤) litellm/proxy/spend_tracking/spend_management_endpoints.py:1641-1644, 1791-1796, 1893-1907
前端 Filter 配置 ui/litellm-dashboard/src/components/view_logs/index.tsxlogFilterOptions
前端过滤逻辑 ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsxFILTER_KEYS.SESSION_ID

修改 trace_id 相关代码后的本地验证流程

  1. 改完代码,本地 make run 启动 LiteLLM
  2. python scripts/trace-id/test_trace_id_behavior.py --wait 15
  3. 检查 5 个场景:A/B/C/D 必须 ✅,E 维持 ❌
  4. 如果改了 UI:手动打开 /ui/logs,Filter Session ID 输入步骤 2 测试出的某个值,确认能查到
  5. 如果改了响应头逻辑:curl -i 验证响应头里同时回写 x-litellm-trace-idx-trace-id