核心问题
系统上线后,我们怎么知道它发生了什么?
测试回答的是:
在已知场景里,系统是否符合承诺?
可观察性回答的是:
在真实世界里,当系统出现未知问题时,我们能不能理解它?
生产环境永远比测试环境复杂:
- 用户行为更多样
- 数据状态更脏
- 网络更不稳定
- 第三方服务会失败
- 并发和时序问题会出现
- 需求边界会被真实使用撞出来
所以系统不能只在测试里“正确”,还必须在运行时“可被理解”。
可观察性不是多打日志
很多人把 observability 理解成:
出问题了,多加点 log。
这太晚了。
真正的可观察性是:
系统在设计时就留下足够证据,让未来的你能回答问题。
例如课程平台里,用户反馈:
我付了钱,但看不了课程。
没有可观察性的系统只能猜:
- 支付成功了吗?
- purchase 创建了吗?
- access grant 创建了吗?
- grant 被撤销了吗?
- 账号被封禁了吗?
- 课程下架了吗?
- 缓存是不是没刷新?
有可观察性的系统应该能沿着一条证据链查清楚:
payment.succeeded
-> purchase.created
-> course_access_grant.created
-> canAccessCourse=false
-> reason: account_suspended
三根支柱:Logs, Metrics, Traces
传统上,可观察性有三根支柱:
- Logs:发生了什么
- Metrics:整体表现如何
- Traces:一次请求经过了哪里
它们不是互相替代,而是回答不同问题。
Logs:记录关键事实
日志适合记录具体事件和上下文。
例如:
logger.info("course_access_grant_created", {
accountId,
courseId,
source: "purchase",
purchaseId,
requestId,
})
好日志不是自然语言作文,而是结构化事实。
坏日志
User access failed
问题是它没有告诉我们:
- 哪个用户?
- 哪门课?
- 为什么失败?
- 发生在哪个请求里?
- 和哪个 payment 或 purchase 有关?
好日志
logger.warn("course_access_denied", {
accountId,
courseId,
reason: "account_suspended",
requestId,
})
日志名应该像事件名,而不是随手写一句话。
Metrics:观察系统健康
指标适合回答整体趋势。
例如:
payment_success_count
purchase_created_count
course_access_grant_created_count
course_access_denied_count
refund_count
outbox_event_lag_seconds
指标能发现“局部日志看不出来”的问题。
例如:
payment_success_count 正常
purchase_created_count 正常
course_access_grant_created_count 突然下降
这说明支付和购买流程还在跑,但访问权发放出了问题。
好的指标应该围绕业务承诺,而不只是机器状态。
机器指标重要:
- CPU
- memory
- latency
- error rate
但业务指标也重要:
- 支付成功后访问权发放成功率
- 退款后访问权撤销成功率
- 企业课程分配成功率
- 课程播放失败率
- 登录失败率
Traces:理解一次请求的路径
Trace 适合回答:
这一次请求到底经过了哪些步骤,慢在哪里,失败在哪里?
例如购买流程:
POST /checkout
-> create_payment
-> payment_provider.confirm
-> handle_payment_succeeded
-> purchase.create
-> course_access_grant.create
-> outbox_event.create
如果用户说“支付成功但不能看课”,trace 可以告诉我们:
- 请求有没有到达服务
- 哪一步耗时最长
- 哪一步失败
- 是否写入数据库
- 是否创建 outbox event
Trace 的关键是 correlation id:
requestId
paymentId
purchaseId
accountId
courseId
没有关联 ID,日志、指标和 trace 就很难串起来。
可观察性要围绕问题设计
不要先问:
我们要接哪个监控工具?
先问:
生产出问题时,我们最需要回答哪些问题?
课程平台至少要能回答:
- 用户为什么不能访问课程?
- 支付成功后访问权是否发放?
- 退款后访问权是否撤销?
- 企业管理员为什么分配失败?
- 邮件为什么没有发送?
- 推荐系统延迟多久?
- 哪些课程播放失败最多?
- 哪些接口错误率突然升高?
每个问题都应该对应证据:
| 问题 | 证据 |
|---|---|
| 为什么不能访问课程 | access decision log + reason |
| 支付后是否发放访问权 | payment/purchase/grant 指标和日志 |
| 退款后是否撤销访问权 | refund/revoke event |
| 邮件为什么没发送 | outbox event + worker result |
| 哪些接口变慢 | latency metrics + traces |
记录决策原因,而不只是结果
权限和访问判断尤其要记录原因。
坏做法:
return false
更好的做法:
return {
allowed: false,
reason: "account_suspended",
}
然后在边界处记录:
logger.warn("course_access_denied", {
accountId,
courseId,
reason: decision.reason,
requestId,
})
这会极大提高排查效率。
当然,不是所有内部函数都要返回复杂对象。关键是:在重要业务边界上,系统要能解释自己的决定。
Alert:告警不是噪音机器
告警的目标不是“任何异常都通知人”,而是:
当系统承诺正在被破坏时,通知能采取行动的人。
坏告警:
CPU > 80%
这不一定是坏事,也不一定需要人处理。
更好的告警:
payment_success_count 正常,但 course_access_grant_created_count 低于预期
这说明用户可能付了钱但拿不到访问权,是业务承诺被破坏。
好的告警应该满足:
- 明确影响什么承诺。
- 有明确负责人。
- 有可执行的排查入口。
- 噪音足够低。
如果告警经常没人理,它就已经失效了。
可观察性和隐私
可观察性不是把所有数据都打进日志。
不要记录:
- 密码
- token
- 完整支付卡信息
- 隐私文本
- 不必要的个人身份信息
可以记录:
- 内部 ID
- 脱敏后的邮箱或手机号
- 枚举化 reason
- 业务对象 ID
- 请求 ID
原则:
记录足够排查问题的证据,不记录不必要的私人信息。
可观察性也是产品能力
可观察性不只是给工程师看的。
后台系统里,很多“客服查询页”“订单详情页”“访问权历史页”,其实都是面向运营和客服的可观察性。
例如客服需要回答:
用户为什么不能看课?
一个好的内部页面应该展示:
账号状态:suspended
购买记录:paid
退款记录:none
访问权:created, currently inactive
拒绝原因:account_suspended
这比让客服找工程师查数据库成熟得多。
小结
- 测试验证已知场景,可观察性帮助理解未知问题。
- 可观察性不是多打日志,而是设计证据链。
- Logs 记录关键事实,Metrics 观察整体趋势,Traces 理解单次请求路径。
- 好日志应该结构化,像事件名,而不是自然语言碎片。
- 指标应该围绕业务承诺,不只是机器状态。
- 权限和访问判断要记录原因。
- 告警应该在系统承诺被破坏时触发。
- 可观察性必须尊重隐私和数据最小化。
- 内部查询页也是一种产品化的可观察性。