Skip to content

日志、指标与可观测性 ​

系统出问题时,最贵的往往不是修复本身,而是找到问题在哪里。很多团队都经历过:

  • 系统已经出问题半小时了,是用户在群里反馈后才知道的;
  • 平均响应时间只有 200ms,监控很好看,用户却在抱怨卡;
  • 日志每天几百 GB,但想找某个用户的某次请求时,怎么也找不到;
  • 告警每天响几十次,大家已经习惯性忽略,真正的故障也被忽略了。

可观测性要回答的是:发生了什么、影响了谁、问题出在哪一步。

读完这篇你会

  • 分清指标、日志、链路追踪各自擅长回答什么问题
  • 写出能被检索和关联的结构化日志
  • 设计有用的指标和告警,并在故障后做有效的复盘

1. 原理:仪表盘、行车记录仪和导航轨迹 ​

开车时有三样东西帮你了解车况:

信号类比擅长回答例子
指标(Metrics)仪表盘整体状况正常吗?趋势如何?错误率、响应时间、请求量、队列积压
日志(Logs)行车记录仪某一次具体发生了什么?某个请求的参数、校验失败的原因、异常堆栈
链路追踪(Traces)导航轨迹一次请求经过了哪些服务,每一段花了多久?网关 30ms → 订单服务 80ms → 数据库 1.2s

三者配合使用:指标发现异常 → 链路追踪定位到哪一段慢或出错 → 日志查看那一次的具体细节。

2. 结构化日志:让日志能被检索 ​

text
❌ 2026-10-23 10:00:01 ERROR 支付失败了

这样的日志无法回答“哪个用户、哪个订单、为什么失败”。用 JSON 格式记录,并带上关键字段:

json
{
  "time": "2026-10-23T10:00:01.234+08:00",
  "level": "error",
  "service": "order-api",
  "version": "a1b2c3d",
  "requestId": "req_7f3a9c",
  "event": "payment.failed",
  "orderId": "ord_20261023_0042",
  "reason": "channel_timeout",
  "durationMs": 10003
}

有了这些字段,就可以在日志系统里按 requestId 找到一次请求的全过程,按 event 统计某类事件的数量,按 version 判断是不是新版本引入的问题。

日志级别的约定:

级别什么时候用
error需要有人处理的异常,通常对应告警
warn预期之外但系统能自行恢复的情况(重试成功、降级)
info重要的业务事件(下单、支付、登录)
debug排查问题用的细节,生产环境默认关闭

3. 指标:从用户和资源两个角度看 ​

面向服务的三个核心指标(RED):

  • 请求速率(Rate):每秒多少请求;
  • 错误率(Errors):失败的比例;
  • 耗时(Duration):响应时间的分布。

面向资源的指标:CPU、内存、数据库连接池使用率、队列长度。

业务指标:下单成功率、支付成功率、注册转化率。技术指标都正常,不代表用户没有受影响;业务指标突然下跌,往往是最直接的故障信号。

4. 坑一:平均响应时间很好看,用户却说很卡 ​

现象:监控显示平均响应时间 200ms,但用户经常抱怨页面要等好几秒。

原因:平均值会把少数很慢的请求“平均掉”。假设 95 个请求耗时 100ms,5 个请求耗时 2 秒,平均值只有 195ms,看起来很健康,但每 20 个请求里就有 1 个要等 2 秒。对于一个要发十几个请求的页面,几乎每次打开都会碰到慢的那个。

修复:看分位数:

  • p50(中位数):一半请求比它快;
  • p95:95% 的请求比它快,反映大多数用户的“较差体验”;
  • p99:最慢的 1% 请求,常常暴露锁等待、慢查询、GC 停顿等问题。

告警和优化目标都应该基于 p95、p99,而不是平均值。

5. 坑二:日志要么查不到,要么多到查不动 ​

现象:出问题时想找日志,发现关键步骤根本没记录;另一边,某个循环里每处理一条数据就打一条日志,每天产生几百 GB,日志系统又慢又贵。

修复:

  • 在关键节点记录:请求入口和出口、业务状态变化、外部调用的结果和耗时、所有错误;
  • 不要在高频循环里逐条打日志,改为处理完后记录汇总(处理了多少条、失败多少条、耗时多久);
  • 每条日志都带上请求 ID,详见 错误处理与请求追踪;
  • 对量很大、价值不高的日志进行采样。

6. 坑三:告警太多,最后没人看了 ​

现象:每天几十条告警,大部分是“CPU 短暂超过 80%”这类很快自己恢复的情况。大家慢慢习惯了忽略告警,一次真正的数据库故障也被淹没了。

修复:

  • 对症状告警,而不是对原因告警:优先告警“用户受到了影响”的信号(错误率上升、p99 变慢、支付成功率下跌),而不是所有可能的原因(CPU 高不一定影响用户);
  • 每条告警都要有处理说明:触发后应该先看什么、可能的原因、怎么处理。说不出“收到之后该做什么”的告警,就不该存在;
  • 定期回顾告警:一个月内频繁触发又无需处理的告警,调整阈值或删除。

7. 坑四:日志里有密码和手机号 ​

现象:为了排查问题,把整个请求体打进了日志,里面包含用户的密码、身份证号、手机号。日志系统的访问权限通常比数据库宽松得多,这些数据就这样泄露给了更多人。

修复:

  • 密码、token、验证码永远不记录;
  • 手机号、身份证号、地址等个人信息脱敏后再记录(如 138****0000);
  • 在日志库里统一配置敏感字段的自动脱敏,而不是依赖每个人记得。

8. 坑五:新版本有问题,却看不出来 ​

现象:一次发布后,某个接口的错误率从 0.1% 升到了 2%,但因为整体错误率变化不大,过了两天才被发现。

修复:

  • 所有指标和日志都带上版本号,发布后按版本对比错误率和响应时间;
  • 发布后的半小时内,重点观察关键指标;
  • 灰度发布时,对比新旧版本的指标,有明显差异就暂停发布。

9. 跨服务串联:链路追踪 ​

一个请求经过网关、多个服务和数据库,只靠各自的日志,很难看出时间花在了哪一段。链路追踪会给整个请求分配一个追踪 ID,并通过请求头(标准格式是 W3C 的 traceparent)在服务之间传递,记录每一段的开始时间和耗时。

现在通用的做法是使用 OpenTelemetry:它提供了各种语言的 SDK,可以自动为 HTTP 请求、数据库查询等生成追踪数据,再发送到你选择的分析平台。前端的错误上报如果也带上同一个追踪 ID,就能把“用户在页面上看到的报错”和“后端某个服务的某次慢查询”串起来。

10. 故障复盘:让同样的问题不再发生 ​

每次重要故障后,写一份复盘,重点是改进系统,而不是追究个人:

text
时间线:什么时候发生、什么时候发现、什么时候恢复
影响:影响了多少用户、哪些功能、持续多久
发现方式:是监控告警发现的,还是用户反馈的?(如果是后者,说明监控有盲区)
根本原因:为什么会发生(连续问几个“为什么”)
改进项:每一项都有负责人和完成时间
        例如:补充告警、增加测试、加限流、改进发布流程

排查思路:从告警到根因 ​

  1. 看指标确认范围:是所有接口还是某一个?所有用户还是某一部分?从什么时候开始的?和发布、配置变更、流量变化的时间吻合吗?
  2. 用链路追踪定位:找一个慢的或失败的请求,看时间花在了哪个服务、哪一步。
  3. 用日志看细节:按请求 ID 或追踪 ID 查看那一次的完整日志,找到具体的错误和参数。
  4. 回到代码和变更:结合最近的发布记录,找到导致问题的改动。

检查清单 ​

  • [ ] 日志是结构化的,带有请求 ID、服务名和版本号
  • [ ] 日志级别有明确约定,高频循环中不逐条打日志
  • [ ] 监控 p95 / p99,而不只是平均值
  • [ ] 有业务指标,并对用户受影响的症状告警
  • [ ] 每条告警都有处理说明,定期清理无效告警
  • [ ] 敏感信息不进日志或自动脱敏
  • [ ] 重要故障有复盘,改进项有负责人和期限

延伸阅读 ​

为复用而记录,为理解而整理。