日志不是"打得越多越好"
AI 系统的日志常见两个极端 ✗:只打一行"请求成功" ✔(出问题查不出 ✔),
或者把整个请求体全塞进去 ✔(日志爆炸、还带敏感数据 ✔)。
**目标**:让日志成为**可查询的证据** ✔ —— 出问题时能用几条查询定位 ✔。
一、字段规范化(一次定好,长期受益)
**必带**(每条日志都有 ✔):
```
ts(UTC ISO8601 ✔)| trace_id | request_id | user_id/tenant_id(脱敏 ✔)
feature(功能名 ✔)| stage(阶段:prepare/retrieve/generate/validate ✔)
status | error_code(稳定枚举 ✔)| latency_ms(分段 ✔)
model | prompt_version | index_version(见可复现那篇 ✔)
```
**按需**(大字段单独存,日志只放引用 ✔):
```
input_hash | output_summary(截断 ✔)| retrieved_chunk_ids(片段 id 列表 ✔)
cost(token 数 + 折算 ✔)| retry_count
```
**要点**:**阶段与分段延迟**最常被省 ✗ —— 没有它就无法回答"慢在哪一段" ✔。
二、三条纪律
1. **结构化**(JSON 行 ✔,不要自由文本 ✔)—— 否则无法聚合 ✔;
2. **字段名稳定**(改名等于破坏历史查询 ✔ —— 要改就加新字段 ✔);
3. **脱敏**(日志也是数据 ✔ —— 见脱敏那篇 ✔)。
三、四条该能"一次查询搞定"的问题
```
① 某 trace_id 的完整链路(含上游与工具调用 ✔)
② 某功能最近 1 小时的失败率与错误码分布 ✔
③ 某次调用的 prompt_version / index_version / 检索片段 ✔
④ 某租户今日用量与成本 ✔
```
**做不到,说明字段缺项或不成结构** ✗。
四、两个反模式
五、落地顺序
1. 先统一 `ts/trace_id/feature/stage/status/latency_ms` 六个字段 ✔(改动小、收益大 ✔);
2. 再加 `model/prompt_version/index_version` ✔;
3. 大字段单独存,日志里放 `input_hash` 与 `chunk_ids` ✔;
4. 最后把上面那四条问题写成现成查询(值班手册里 ✔)。
一条判据
> 值班时遇到"某用户说刚才的回答不对" ✔,你能否在 **5 分钟内**用日志还原"当时模型看到了什么" ✔?
> 不能,说明日志还不是证据 ✗。