日志、指标、Tracing 的边界,不是按工具划的,是按决策成本划的
指标负责发现异常,Tracing 负责收窄路径,日志负责还原现场;三者混用,只会把排障成本一起抬高
很多团队补可观测性,第一反应是“把日志、指标、Tracing 都接上”。
这句话听起来没错,真正出问题的是下一步:三件东西都接上了,但边界没定,于是每一种手段都在替另外两种擦屁股。
结果通常很眼熟:指标标签爆炸,Tracing 采样一路上调,日志量每月翻倍,告警还是不准,排障还是靠人肉 grep。工具一个没少,系统却没有真正变得更可观察。
我的判断是:日志、指标、Tracing 的边界,不该按“数据长什么样”来划,而该按“你下一步要做什么决策”来划。
- 指标回答的是:要不要立刻处理,影响范围大不大;
- Tracing 回答的是:问题大概卡在链路哪一段;
- 日志回答的是:那一段代码在那个请求里到底发生了什么。
三者不是信息量递增的关系,而是决策成本递增的关系。指标最便宜,适合持续盯盘;Tracing 更贵,适合收窄范围;日志最重,适合最后还原现场。
如果你让日志承担报警,让 Tracing 承担审计,让指标承载每个用户、每个订单、每个 SQL 维度,系统当然也能跑,但代价会非常诚实地出现在存储、查询、采样、基数控制和人力排障上。
真正先做决定的,应该是指标
线上出了毛刺,第一步通常不是“看细节”,而是“判断值不值得立刻处理”。
这一步最适合指标,不是因为指标更高级,而是因为它最便宜。
一个好的指标系统,应该让你在几十秒内回答下面几件事:
- 错误率是在抬头,还是只是偶发噪声;
- 延迟变差是全局性的,还是只集中在某个接口;
- 影响是一台机器、一个机房,还是整条调用链;
- 问题是刚发生,还是已经持续半小时。
这些问题本质上都在做同一件事:用低成本聚合信息,换取高价值的行动判断。
所以指标最重要的设计原则不是“字段越多越好”,而是“聚合后还能支持决策”。
很多团队把指标做坏,往往不是监控埋少了,而是把本不该放进指标的高基数字段硬塞进去,比如 user_id、order_id、trace_id、完整 URL、动态错误消息。这样做短期很爽,感觉什么都能切;中期就会开始看到存储膨胀、查询变慢、告警维度失控;长期的结果通常是团队自己把指标平台用怕了。
指标适合表达的是稳定维度下的趋势和分布,比如接口名、状态码、机房、依赖服务、缓存命中与否。这些维度的价值不在于“还原具体一次请求”,而在于帮助你快速判断问题是否成片发生。
一旦你想问的是“到底哪一个订单失败了”,那就已经不是指标该独自回答的问题了。
Tracing 的价值,不是记录一切,而是缩短定位路径
Tracing 经常被误解成“比日志更高级的日志”。这会直接把它用废。
Tracing 真正擅长的,是描述一次请求跨服务、跨组件的路径和耗时关系。它天然适合回答这种问题:
- 慢,是慢在网关、应用、数据库还是外部依赖;
- 一次重试,是在哪一跳被放大的;
- 某个接口的 P99 飙高,是否和某个下游服务相关;
- 一个异常请求在经过哪些服务后开始偏离正常路径。
也就是说,Tracing 提供的是链路结构,不是完整现场。
所以它最该承载的是路径信息、关键阶段耗时、少量能帮助筛选的标签,而不是把业务对象完整塞进去,更不是把每个局部变量都变成 span attribute。
我见过不少团队一上 tracing,就想把它做成统一真相源:SQL 原文要挂、用户输入要挂、完整响应体要挂、所有重试参数要挂。最后的结果不是“排障更快”,而是:
- span 体积迅速变大,采样率被迫下调;
- 查询链路时噪声远大于信号;
- 真到事故现场,关键请求反而因为采样没留下来;
- 敏感数据治理开始变成新的维护成本。
Tracing 最有价值的时候,不是信息最多的时候,而是你能用一条链路迅速判断下一跳应该看哪个服务、哪个 span、哪一段日志的时候。
如果它已经重到不能全局稳定保留,或者重到查询一次都很痛苦,那说明边界已经越过了。
日志不是第二套指标系统,它是最后的取证材料
日志最容易让人误判,因为它看起来什么都能装。
确实,日志最接近现场。分支怎么走的、参数长什么样、重试第几次、命中了哪个降级路径、为什么这次返回了一个看似成功却语义错误的结果,很多时候都只能从日志里看出来。
但也正因为它信息熵最高,所以它最不适合承担“全局持续观察”这件事。
把日志当主监控源,常见后果有三个。
第一,查询成本高。 你每次都在从原始事实里临时拼结论,速度慢,稳定性也差。
第二,噪声极大。 日志量一大,团队就会自然减少打印;一旦减少打印,关键分支又经常缺证据;最后大家会在“太多看不完”和“太少看不出”之间来回摆。
第三,它会诱导错误的工程习惯。 很多开发者一遇到问题就多打一层日志,结果系统不是更清晰,而是更吵。真正该补的是边界清楚的事件日志、带上下文的错误日志和可关联的 request id,而不是更多“进入方法”“离开方法”“开始处理”“处理完成”。
日志最合适的位置,是在你已经靠指标判断出“这里真的有问题”,靠 tracing 知道“问题大概出在这里之后”,再用它还原具体一次请求。
换句话说,日志应该服务于取证,而不是承担巡逻。
一个简单分工,比“三件都打满”更有效
我更推荐的做法很朴素:先定义排障顺序,再定义采集边界。
可以把一次线上排障拆成三步。
- 先用指标判断有没有系统性问题;
- 再用 tracing 把问题收敛到服务和链路阶段;
- 最后用日志解释那次请求为什么走成这样。
如果你的埋点设计不能支持这个顺序,通常不是工具不够,而是职责分错了。
下面是一种相对克制的写法:
func CreateOrder(ctx context.Context, req CreateOrderReq) error {
start := time.Now()
defer metrics.OrderCreateLatency.Observe(time.Since(start).Seconds())
ctx, span := tracer.Start(ctx, "order.create")
defer span.End()
span.SetAttributes(
attribute.String("payment_provider", req.Provider),
attribute.Bool("has_coupon", req.CouponID != ""),
)
err := service.Create(ctx, req)
if err != nil {
metrics.OrderCreateErrors.WithLabelValues(classify(err)).Inc()
logger.Error("create order failed",
"request_id", requestid.FromContext(ctx),
"provider", req.Provider,
"err", err,
)
return err
}
return nil
}
这里面有三个边界是故意保持克制的。
- 指标只保留聚合后仍然有决策价值的维度,比如错误分类;
- tracing 只挂少量能帮助筛选路径的属性,而不是整个请求体;
- 日志只在失败路径记录关键上下文,并且保证能通过
request_id关联到链路。
这套设计不花哨,但它解决的是一个很现实的问题:出了事之后,团队能不能按成本从低到高地逼近答案,而不是一上来就扎进最贵的数据里。
一个常见误区:把“可观测性统一”理解成“数据全部汇总”
现在很多平台都在讲统一观测。这件事本身没问题,问题出在实现方式上。
有些团队把统一理解成“所有数据进一个平台,所有字段彼此打通,最好一次查询解决所有问题”。这种想法很诱人,因为它像是在消灭工具边界;但工程上,它经常是在消灭成本边界。
统一的正确方向,不是让三种数据长得越来越像,而是让它们能低摩擦地关联起来。
比如:
- 指标告警能直接跳到相关服务和时间窗;
- tracing 能按
request_id或错误码找到对应日志; - 日志能带上 trace 上下文,而不是各自为政。
这叫联通,不叫混用。
真正危险的不是平台多,而是所有问题都只能靠最重的那层数据来回答。
反例和边界:不是所有系统都需要三件套拉满
也要承认,有些场景里不必把边界讲得这么完整。
低 QPS 的内部工具、单机批处理脚本、非常稳定的离线任务,日志就可能已经够用;请求链路很短、依赖关系很简单的服务,tracing 的收益未必有想象中高;某些受合规约束的审计场景,也不该指望 tracing 或普通应用日志来充当正式审计记录。
所以这篇文章不是在说“每个系统都必须把日志、指标、Tracing 配齐”,而是在说:只要你决定做三件事,就不要让它们互相替班。
系统越复杂,职责越需要收束。否则今天省掉的是设计边界,明天补上的就是平台账单、排障时间和团队认知负担。
结尾
可观测性最怕的,不是工具少,而是每一层都想回答所有问题。
指标应该让你尽快决定是否行动,Tracing 应该让你尽快决定先看哪里,日志应该让你尽快知道到底发生了什么。
这三个动作顺序一乱,采得再全,也只是把排障成本埋得更深。