先决定:一条日志是一个事件,不是一句话
先决定:一条日志是一个事件,不是一句话
很多团队的日志字段混乱,根源是把日志当成「给人看的字符串」。一旦这么想,字段就永远长不出来,只能不断往 message 里塞内容。正确的心智模型是:一条日志 = 一个结构化事件,它有几个固定维度(谁、在哪、干了什么、结果如何),外加少量该事件特有的属性。
实操上可以先用一句话检验字段设计是否合格:把这条日志的所有字段(不含 message)交给一个从没看过代码的人,他能不能说出「什么服务、在哪个实例、处理谁的什么请求、成功还是失败」。如果答不上来,说明维度字段缺失,而不是 message 写得不够详细。
固定字段:少而稳定,值得写进规范
固定字段的价值在于可以跨服务聚合查询,所以数量要克制。经验值是一组核心字段控制在 10 个以内,多一个都要有明确用途。一套能直接落地的基线:
timestamp:ISO 8601 带时区,如2024-05-11T08:12:33.421+08:00。别用本地时间字符串,跨时区排查会立刻翻车。level:只用debug/info/warn/error/fatal,不要自创notice、critical之类。service、version:服务名用部署单元名,version 用 Git SHA 或语义化版本,便于「这个问题只在新版本出现」这类判断。env:prod/staging/dev,防止在测试环境里查生产问题。instance或pod:能定位到具体进程,排查 OOM、连接池打满时必用。trace_id、span_id:与链路追踪打通,这是最有性价比的一个字段。event:稳定的机器可读事件名,如order.create.failed,而不是自然语言。message:人类可读描述,允许拼接变量,但不要只依赖它做检索。
命名统一用 snake_case,单位写进字段名:duration_ms、size_bytes,永远不要出现含义不明的 duration。
动态字段:放进一个命名空间,别污染顶层
业务属性(订单号、用户 ID、支付渠道、重试次数)属于动态字段。常见错误是全部平铺到顶层,导致同一个索引里字段爆炸,几十个服务各写各的 user、uid、userId。
建议统一收进一个对象字段,比如 attrs(或 fields):
- 顶层只留固定维度,动态内容全部进
attrs.*,聚合查询时按前缀过滤,边界清晰。 - 高频检索的少数几个可以「提升」为顶层,但要有规范文档说明为什么。
- 值类型保持稳定:同一个键不要今天是字符串、明天是数字,否则写入 Elasticsearch 时会直接映射冲突。
- 禁止把整个请求体、整个响应体序列化塞进去。日志不是数据备份,单个字段值超过 1KB 就该拆分或截断。
敏感信息在序列化层统一处理:手机号、身份证、token、Cookie 走脱敏函数,不要指望每个调用点自觉。日志里泄露凭据是最常见的安全事故之一。
错误日志:把「为什么失败」变成可查询字段
错误日志如果只有一句 调用失败,等于没打。至少要有四类信息:error.type(异常类名或错误码)、error.message、error.stack(可截断,前 20 行通常够用)、error.retryable(布尔值)。
- 区分「业务失败」和「系统异常」:前者用
warn加错误码,后者用error加堆栈。把用户输错密码打成 error,会淹没真正的故障告警。 - 外部依赖调用要记
dependency、duration_ms、status_code,这样能直接算出某个下游的失败率和 P99。 - 重试场景加
attempt字段,否则一次请求打出 3 条相同错误日志,看起来像 3 次故障。 - 日志级别要能在运行时动态调整。生产默认
info,出问题时把某个 logger 调到debug,比重新发版快得多。
落地时的三个工程约束
最后是容易被忽略的执行细节。第一,序列化必须由框架统一完成:业务代码只传键值对,禁止自己 json.dumps 后拼进字符串,否则迟早出现双重转义和非法 JSON。第二,字段写入前做白名单校验:在开发环境对不符合规范的字段名直接报错,比在生产环境发现字段对不上要便宜得多。第三,给字段写文档并做 CI 检查:把固定字段清单放在仓库里,用一条测试断言日志结构,防止新人随手加字段破坏既有查询和看板。日志字段是接口,是接口就要有契约。
- 点赞
- 收藏
- 关注作者
评论(0)