SuperAPI AI 工具集

logging-backend

部署与运维

查看项目

更新于 10-16 06:00

技能介绍

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 - 实践实现指南