AI 应用日志别再 print 了:结构化日志 + request_id 贯穿(O01)
系列《AI 应用生产化手册》第 6 篇(共 30 篇)|配套开源项目:github.com/ChenYingbo/... 模块一(评测工程)5 篇已完结,从本篇进入模块二:可观测性
一、先看三个"盲人摸象"时刻
- 用户说"回答好慢"------多慢?哪个环节慢?是模型慢还是网关慢?你答不上来。
- 线上回答质量突然变差------是换模型了?提示词被改了?还是某类问题本来就差?没有历史日志,无从对比。
- 请求失败了,日志里只有一行
ERROR: xxx------没有 request_id、没有上下文、没有当时模型看到了什么,根本没法排查。
根因只有一个:你的 AI 应用"不可观测"------出了事你只能靠用户描述和猜。
核心认知:AI 应用观测的第一层不是炫酷的追踪平台,而是"结构化日志"。追踪平台是锦上添花,结构化日志是地基。
二、原理:为什么传统观测对 AI 应用失效
传统观测三支柱(Metrics / Logs / Traces)在 AI 应用上有三个失效点:
| 传统假设 | AI 应用的现实 | 后果 |
|---|---|---|
| 输出是确定的,日志能比对"预期" | 输出是概率性的,没有"预期值" | 日志只能记录,不能断言对错 |
| 一次请求一次调用 | 一次问答 = 多步(检索→LLM→工具→再生成) | 单条日志看不出完整链路 |
| 上下文无关 | 答案质量强依赖上下文 | 不知道"模型当时看到了什么",无法定位答错原因 |
结论:AI 应用观测的关键不在"多打日志",而在"打对什么"------尤其是把输入、上下文、中间步骤记录下来。
AI 应用该观测的六类信息
| 类别 | 记录什么 | 回答什么问题 |
|---|---|---|
| ① 输入输出 | 问题(脱敏后)、答案长度 | "用户问的什么、模型答的什么" |
| ② 思考过程 | 推理步骤、中间结果(Agent) | "模型怎么得出这个答案的" |
| ③ 工具调用 | 工具名、参数、返回值、耗时 | "工具用对没有、卡在哪" |
| ④ 上下文 | 系统提示词版本、检索到的文档 | "模型看到了什么"(定位幻觉的关键) |
| ⑤ 成本 | in/out tokens、模型 | "这次回答花了多少钱" |
| ⑥ 延迟 | 总耗时、各环节耗时 | "慢在哪一环" |
结构化日志 vs 自由文本日志
| 自由文本(print) | 结构化(key=value) | |
|---|---|---|
| 示例 | 模型返回了,用了 3 秒 |
event=llm_call model=chat-cheap in_tokens=69 out_tokens=85 latency_ms=1766 |
| 检索 | grep 只能按人话猜 | 按字段精确过滤 |
| 对比 | 无法程序化分析 | 可解析、可聚合、可入看板 |
| 串联 | 没有 request_id | 全链路同一 request_id |
结构化 = 机器可读。机器可读 = 可以聚合、告警、画图------这就是监控看板的地基。
request_id:把碎片串成链路
ini
grep request_id=a1b2c3d4e5f6 app.log
→ 完整还原一次请求的生命周期(耗时、token、哪一步失败)
三、动手:装上结构化日志
bash
git clone https://github.com/ChenYingbo/ai-prod-demo.git && cd ai-prod-demo
cp .env.example .env # 填 DEEPSEEK_API_KEY
docker compose up -d litellm
source .venv/bin/activate && pip install -r requirements.txt
uvicorn app.main:app --port 8000
制造一次真实请求,观察日志:
bash
curl -s -X POST http://localhost:8000/api/chat \
-H "Content-Type: application/json" -d '{"question":"什么是 RAG?"}' | head -c 120
你会看到同一 request_id 串起的完整链路(这是配套项目的真实日志):
ini
event=chat_request request_id=05e83d83c150 question_len=13
event=llm_call request_id=05e83d83c150 model=chat-cheap in_tokens=69 out_tokens=85 latency_ms=1766
event=chat_response request_id=05e83d83c150 answer_len=207 latency_ms=1889
三条验证断言
- 同一 request_id:三个事件 request_id 一致 → 链路可串联
- 字段可检索 :
grep -c "event=llm_call" app.log能数出调用次数 - 错误可见 :
grep "event=chat_error"能看到 502 的延迟和错误摘要
这些日志能回答什么
- "慢在哪一环?" →
chat_response.latency_msvsllm_call.latency_ms对比 - "平均每次请求花多少 token?" →
llm_call.in_tokens求和 - "今天失败了多少次?" →
chat_error计数
四、真实踩坑
- 日志打全量输入 :问题全文进日志 = 隐私风险 + 体积爆炸------只打
question_len - 没有 request_id:一堆日志散落无法串联------中间件必须为每个请求生成并贯穿
- LLM 日志和业务日志混在默认 logger :
uvicorn.access默认日志是双份噪音------关掉,统一走结构化 - (实测)第三方库噪音 :openai SDK 底层 httpx 会把每次 HTTP 请求打成 INFO,直接淹没业务日志------
httpx/httpx2/openai/httpcore/urllib3全部压到 WARNING。特别提醒:新版 openai 的内部 logger 叫httpx2(不是 httpx),只压 httpx 是不够的 - 只在错误时打日志:成功路径也要打------没有成功基线就没法对比退化
- 时间戳没有时区:跨机器对比全是坑------统一 UTC ISO8601
- 打印配置的模型名而不是实际模型 :配置写
chat-strong,实际调deepseek-chat------要打resp.model(实际值)
五、小结
- 传统观测三失效:输出不确定 / 多步调用 / 上下文依赖
- 六类信息:输入输出、思考过程、工具调用、上下文、成本、延迟
- request_id 是地基------追踪平台本质就是"自动化的 request_id 串联"