日志又多又乱?用结构化日志定位问题

线上报警响了,你打开日志,看到成千上万行文本,靠关键词搜索翻了半小时,终于找到一条可疑记录,却不知道它属于哪个用户、哪次请求、上游是哪个服务。日志量不是问题,问题在于它没有结构——人写的时候方便,机器和故障排查时极其低效。

文本日志的三个硬伤

  • 无法可靠查询:要按字段过滤,只能靠字符串匹配,容易误伤,也不稳定。
  • 缺少上下文:一条日志只说了“失败”,却不知道是谁的、哪一步、耗时多少。
  • 难以关联:分布式系统里一次请求会跨多个服务,纯文本日志根本无法把同一次调用串起来。

结构化日志到底不一样在哪

结构化日志的核心,是把日志从“一句话”变成“一组键值对加一条消息”。同样是记录一次失败,文本日志写成 订单处理失败, user_id: 12345,结构化日志则要明确记录 event=order.process.faileduser_id=12345order_id=...duration_ms=842error_code=...

差别在于查询方式:前者需要 message LIKE '%user_id:12345%',后者可以直接写 user_id = 12345 AND event = 'order.process.failed'。前者依赖字符串格式,后者依赖字段语义——这就是可维护性的分水岭。

落地时要做的四件事

  1. 定义事件命名规范:用统一的分层命名,例如“模块.对象.动作.结果”。一致的名字才能被聚合和告警。
  2. 固定几个必需字段:时间戳、级别、事件名、请求标识(trace id / request id)、用户标识、耗时。这六个字段定下来,绝大多数排查场景就有依托了。
  3. 上下文要贯穿传递:从请求入口生成一个标识,让它在调用链的每一层日志里都出现。这是把散落日志串成一次完整请求的关键。
  4. 敏感信息要脱敏:手机号、邮箱、身份证号必须在写入前掩码,日志系统往往是数据泄露最常见的出口。

一个对比案例

某服务出现间歇性超时。文本日志时代,团队只能看到每条请求结束时的耗时行,然后靠人工拼接时间相近的记录,花了两天定位。改成结构化日志后,同一个问题只做了三个查询:按 duration_ms > 1000 过滤出慢请求;按 trace_id 展开这些请求的完整调用链,发现耗时都集中在某个下游调用;再按 downstream_service 聚合,确认是单一依赖的问题。整个过程不到二十分钟。

常见误区

把日志当调试打印用。上线前忘记删掉的调试语句,会在关键时刻把信噪比拉到最低。日志应该作为产品的一部分被设计。

只记录发生了错误,不记录上下文。“失败”这个词本身不包含任何可用于修复的信息。

把所有信息都塞进一个字段。把结构化数据拼成字符串,等于放弃结构化日志的全部价值。

日志级别随意。错误全部用最高级别会导致告警疲劳;该报错时用了 info,又会让问题被埋掉。级别必须和实际严重性对应。

行动建议

从下一个服务模块开始,做三件事:定下事件命名规范和必需字段清单;确保请求标识贯穿调用链并写进每条日志;检查一遍是否还有敏感信息未脱敏。改动不大,但下一次线上出问题时,你会真切感受到这二十分钟的投入能省回多少个小时。

标签:#, #, #