日志、指标与可观测性
系统出问题时,最贵的往往不是修复本身,而是找到问题在哪里。很多团队都经历过:
- 系统已经出问题半小时了,是用户在群里反馈后才知道的;
- 平均响应时间只有 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
时间线:什么时候发生、什么时候发现、什么时候恢复
影响:影响了多少用户、哪些功能、持续多久
发现方式:是监控告警发现的,还是用户反馈的?(如果是后者,说明监控有盲区)
根本原因:为什么会发生(连续问几个“为什么”)
改进项:每一项都有负责人和完成时间
例如:补充告警、增加测试、加限流、改进发布流程排查思路:从告警到根因
- 看指标确认范围:是所有接口还是某一个?所有用户还是某一部分?从什么时候开始的?和发布、配置变更、流量变化的时间吻合吗?
- 用链路追踪定位:找一个慢的或失败的请求,看时间花在了哪个服务、哪一步。
- 用日志看细节:按请求 ID 或追踪 ID 查看那一次的完整日志,找到具体的错误和参数。
- 回到代码和变更:结合最近的发布记录,找到导致问题的改动。
检查清单
- [ ] 日志是结构化的,带有请求 ID、服务名和版本号
- [ ] 日志级别有明确约定,高频循环中不逐条打日志
- [ ] 监控 p95 / p99,而不只是平均值
- [ ] 有业务指标,并对用户受影响的症状告警
- [ ] 每条告警都有处理说明,定期清理无效告警
- [ ] 敏感信息不进日志或自动脱敏
- [ ] 重要故障有复盘,改进项有负责人和期限