Logging Backend - Wide Events
Logging Backend 是 testacode/llm-toolkit 中的一个技能,用于实现 Wide Events(也称为 Canonical Log Lines,规范化日志行):每个请求生成一个结构化事件,把全部相关上下文汇总到单条记录中。
核心概念
一个 Wide Event 是一条 JSON 日志,捕获一次请求的完整生命周期。与其分散记录多条日志(“user logged in”“fetched data”“returned response”),不如在请求结束时发出一个包含全部聚合信息的事件。
资料中列出的收益包括:
- 用一个查询调试,而不是关联多条日志
- 对结构化字段进行简单 SQL 分析
- 日志之间没有上下文丢失
- 有效的尾采样(只保留有价值的事件)
传统日志与 Wide Event 的对比(资料示例):
// 传统日志 - 多条分散行
[10:30:00] INFO: User 789 logged in
[10:30:01] INFO: Fetching orders for user 789
[10:30:02] DEBUG: Query executed in 45ms
[10:30:02] INFO: Found 3 orders
[10:30:03] ERROR: Failed to send email notification
// Wide Event - 全部在一条记录中
{
"timestamp": "2024-01-15T10:30:03.000Z",
"user": { "id": "789" },
"action": "fetch_orders",
"result": { "count": 3 },
"infra": { "db_time_ms": 45 },
"error": { "type": "email_failed", "message": "SMTP timeout" }
}
Wide Event 的结构
资料给出的示例结构:
{
"timestamp": "2024-01-15T10:30:00.000Z",
"level": "info",
"service": "api-gateway",
"trace_id": "abc123",
"request": { "method": "POST", "path": "/api/orders", "duration_ms": 245 },
"user": { "id": "user_789", "plan": "premium" },
"business": { "order_id": "ord_456", "total": 150.00, "items_count": 3 },
"infra": { "db_queries": 4, "db_time_ms": 120, "cache_hits": 2 },
"error": null
}
完整字段的 JSON 模式参见 references/wide-event-schema.md。
实施工作流
阶段 1:基础中间件
- 创建中间件,在请求开始时初始化 wide event
- 将事件附加到上下文(
req.wideEvent、AsyncLocalStorage等) - 在请求结束时发出日志
中间件代码参见 references/implementation.md。
阶段 2:富集
- 在每个 handler/service 中向 wide event 添加字段
- 使用类型化 helper:
enrichUser()、enrichBusiness()、enrichError() - 累积指标:queries、cache hits、外部调用
阶段 3:尾采样
- 实现采样函数,决定记录哪些日志
- 总是记录:错误、高延迟、特定用户
- 对普通成功请求按比例采样
尾采样函数参见 references/implementation.md。
阶段 4:着色(开发环境)
- 按级别配置颜色(ERROR=红色,WARN=黄色)
- 按上下文配置颜色(Request=蓝色,User=绿色)
- 格式化终端可读输出
颜色配置参见 references/colorization.md。
快速检查清单
- [ ] 每个请求创建 wide event 的中间件
- [ ] 整条链路可访问上下文(AsyncLocalStorage / req)
- [ ] request 字段:method、path、status、duration_ms
- [ ] user 字段:id、plan、roles(如适用)
- [ ] business 字段:领域实体
- [ ] infra 字段:db_queries、cache_hits、external_calls
- [ ] error 字段:code、message、stack(如有错误)
- [ ] 已配置尾采样
- [ ] 生产环境 JSON 格式、开发环境着色
反模式
常见错误参见 references/anti-patterns.md:
- 将结构化日志与 wide events 混为一谈
- 只依赖 OpenTelemetry
- 每个请求记录多次
- 不包含业务上下文
参考文档
references/wide-event-schema.md- 包含全部字段的完整 JSON 模式references/implementation.md- 中间件与 helper 代码references/anti-patterns.md- 哪些不要做references/colorization.md- 开发环境颜色配置
故障排查
wide event 未捕获用户数据
症状:所有日志中 user: null。
常见原因:认证中间件在 wide event 中间件之后执行。
解决方案:
// 顺序错误
app.use(wideEventMiddleware('my-service'));
app.use(authMiddleware); // 创建事件时 user 还不可用
// 正确顺序
app.use(authMiddleware);
app.use(wideEventMiddleware('my-service')); // user 已在 req 中
infra.db 中的日志为空
症状:db: { queries_count: 0, total_time_ms: 0 }。
常见原因:没有使用追踪 helper。
解决方案:包装查询:
const query = async (sql: string) => {
const start = Date.now();
const result = await db.query(sql);
trackDbQuery(Date.now() - start);
return result;
};
生产环境日志过大
症状:存储/摄取成本高。
解决方案:实施更激进的尾采样:
const shouldLog = (event: WideEvent): boolean => {
if (event.level === 'error') return true;
if (event.request.duration_ms > 1000) return true;
return Math.random() < 0.01; // 其余只保留 1%
};
无法跨服务关联日志
症状:每个服务有自己的 trace_id。
原因:没有传播 header。
解决方案:参见 references/implementation.md 的“跨服务传播”章节。
推荐阅读
- Stripe Engineering - Canonical Log Lines - 推广该模式的原始文章
- Observability Wide Events 101 - 实践实现指南