SuperAPI AI 工具集

logging-best-practices

部署与运维

查看项目

更新于 10-16 10:34

技能介绍

该技能适用于在中大型生产系统中实现日志之前使用,帮助在日志、可观测性或调试策略方面提供指导。其内容改编自 Boris Tane 的文章《Logging sucks. And here's how to make it better.》。

核心理念

日志是为查询优化的,不是为书写优化的——设计时始终以调试为目的。上下文就是一切——在分布式系统中,没有关联 ID(correlation ID)的日志毫无用处。日志是给事故期间的人类看的,不只是为了合规或“以防万一”。如果无法有效地过滤和搜索日志,它们就毫无价值。

结构化日志要求

始终使用键值对(JSON)而不是字符串插值。反例:"Payment failed for user 123"。正例:{"event": "payment_failed", "user_id": "123", "reason": "insufficient_funds", "amount": 99.99}。结构化日志可被机器解析,从而支持聚合、告警和仪表盘。

每条日志事件的必需字段

timestamp——带时区的 ISO 8601(例如 2025-01-24T20:00:00Z)。level——debug、info、warn、error(保持一致,不要自创级别)。event——机器可读的事件名,snake_case(例如 user_login_success)。request_id 或 trace_id——用于关联单个请求内的日志。service——发出该日志的服务/应用。environment——prod、staging、dev。

高基数(high-cardinality)字段示例(可用时始终包含)

user_id、org_id、account_id——受影响的对象。request_id、trace_id、span_id——用于分布式追踪。order_id、transaction_id、job_id——领域特定标识符。这些字段正是让日志在事故期间真正可查询的关键。没有它们,你只能在数百万行中盲目地 grep。应留意那些能帮助你快速定位问题根因的高基数(high-cardinality)字段机会。

上下文传播

在所有服务边界(HTTP 头、消息队列等)传递 trace/request ID。下游服务必须继承上游的关联 ID。使用中间件或拦截器自动将上下文注入每条日志。对于异步任务,存储并恢复原始请求上下文。

日志级别——正确使用它们

debug——本地开发的详细细节,生产环境通常禁用。info——值得记录的常规操作(用户操作、任务完成、部署)。warn——发生了意外但系统已处理(重试、回退)。error——发生了失败且可能需要人工关注(异常、失败请求)。不要为预期内的情况记录错误(例如用户输入错误密码)。

应该记录的内容

请求入口和出口点(含耗时)。状态转换(订单创建 → 已支付 → 已发货)。外部服务调用(含延迟和响应码)。身份认证和授权事件。后台任务的开始、完成和失败。重试尝试和断路器状态变化。

不应记录的内容

敏感数据(密码、令牌、PII、信用卡号)。紧凑循环内的日志(会生成数百万条无用记录)。没有调试价值的成功案例。基础设施已捕获的冗余信息(负载均衡器日志等)。

命名约定

在所有服务之间保持一致——团队应就字段名达成一致。字段名使用 snake_case:user_id,而非 userId 或 user-id。事件使用过去时动词:payment_completed,而非 complete_payment。在有帮助时按域为事件加前缀:auth.login_failed、billing.invoice_created。

性能考量

对生产环境中高流量的 debug 日志使用采样。除非绝对必要,避免在热路径(hot path)内记录日志。缓冲并批量写入日志以减少 I/O 开销。考虑使用无需重新部署即可在运行时更改的日志级别。

事故期间

日志应能回答:谁受影响?什么失败了?何时?为什么?如果查询后 5 分钟内无法回答这些问题,说明你的日志策略需要改进。事故后:补充你当时希望拥有的那些日志。