日志易产品 VP 饶琛琳:日志不会说谎,但 Trace 可能会

Flip.ai 公司 CTO 兼联合创始人 Sunil Mallya 最近发布了一篇文章:《Logs Don't Lie, But Traces Can》。文章中非常尖锐地提出了仅用 Trace 数据做定位分析存在的 8 大问题,并表明这些问题最终还是要通过日志分析解决。这其中有 5 个问题,最终都表现为“Trace 断链”现象。
为了解决 Trace 断链问题的影响,日志易创新性地扩展了遥测管道(telemetry pipeline)在可观测性领域的作用。
在 Gartner 的定义中(https://www.gartner.com/document-reader/document/6906466),遥测管道主要用于消减数据成本。而日志易充分利用自身数据工厂强大的复杂事务处理能力,结合应用日志中的事务信息,可以实现一定程度上的断链 Trace 事务级重关联,最终完整展现全局事务端到端的执行过程。这是对 Sunil Mallya 著文强调“日志不说谎”的实践延伸,让可观测性回归到基于真实执行记录的可靠分析。
在日志易某个客户项目中,通过类似上图的数据工厂配置,日志易针对性实现了相应的数据处理流程,将来自正向调用和异步回调中不同的 TraceID,通过应用日志的 OrderID 关联聚合。这样一来,无论是同步调用还是异步回调,整条业务链路都能通过同一个 OrderID 实现串联查询。
该方法已在金融、能源等多个行业的交易、计费、清结算等核心业务场景中落地验证,可有效解决异步环节的链路断链问题。
下面是原文主要内容的翻译,原文地址:https://www.flip.ai/blog/logs-dont-lie-but-traces-can:
Slack 消息不断弹出,仪表盘变成红色。错误在增加,延迟在飙升。你打开 APM 工具,寻找那条“关键”Trace 信息,期待得到一个明确的答案,但看到的却是数千个看似完全正常的 Span。你滚动、筛选、展开几个节点,没有任何异常突出显示。与此同时,从指标中可以清楚地看出,用户仍在遭遇错误。别无他法,你转向日志。但在这里也并非易事——满屏的干扰信息、不相关错误的堆栈跟踪、混乱无序的时间戳。你进行全局搜索、转换角度,逐行拼凑信息,直到最后,在深处,一个问题赫然出现在你眼前:一个下游服务在默默失效,而这种情况从未被 Trace 记录下来。Trace 信息描绘了一个完美却不真实的情况。监控工具漏掉了它,美观的 Trace 界面只说了一半的真相,但日志却呈现了事实。这就是每个经验丰富的工程师都会学到的残酷教训:日志不会说谎,但 Trace 可能会。
日志
调试中的终极真相来源
每个经验丰富的 SRE 都学会了信任日志,因为日志是捕捉系统内部情况最简单、风险最低的途径。在任何语言中添加一行日志都很容易,不需要 Javaagent、Tracing 框架,也不需要脆弱的上下文传播。logger.info()或 console.error()的性能开销微乎其微,所以你可以随意添加,不用担心会导致系统崩溃。与 Trace 不同,日志不会被外部基础设施采样或拼接,它们是由实际运行的代码直接写入的。这使得日志简单、可靠,且不易出错。当出现故障时,你可以相信日志条目一定会存在,因为它是应用程序自身发出的。
日志也是开发人员和 SRE 最容易理解的信号。你无需 Trace ID 或特殊的用户界面即可开始调查,你可以在文件中进行全局搜索,在日志聚合工具中查询它们,或者围绕从错误代码到用户 ID 的任何信息展开分析。开发人员可以决定日志中包含的内容:堆栈跟踪、请求负载或关联 ID。这种自由度意味着,用于调试故障背后“原因”所需的细节往往已经被捕获。跟 Trace 可能会告诉你某个调用失败了;而日志会向你展示触发该失败的确切异常和参数。正是这种直接性,使得无论你的 APM 或 Tracing 多么先进,解决事件时几乎总会有人去翻阅日志。日志可能杂乱、嘈杂、不够完美,但它是对实际发生情况最可靠的记录。
Trace
强大但并非绝对可靠
分布式 Trace 承诺了一种神奇的功能:当请求在你的服务中穿梭时,它能提供一个完整的端到端映射。理论上,在一个 Trace ID 下,你能获得所有的调用、所有的时间记录以及所有的故障链。有了完善的工具支持,呈现出的图会十分清晰——服务 A 调用服务 B,服务 B 查询数据库 C,你能立即发现延迟产生的位置或错误的源头。这就是为什么 Trace 成为可观测性的三大支柱之一:它能展示仅靠日志无法呈现的关系和并发性。
但现实很少与宣传册上的描述相符。分布式 Trace 的实现依赖于所有环节都正常运行:每个服务都必须进行插桩配置,上下文头信息必须在各个节点间传递,Agent 必须可靠地导出 Span 数据,而且通常你无法捕获所有数据——因此采样机制就会发挥作用。只要错过其中一个步骤,Trace 结果就会产生误导。缺失的 Span 会让人误以为某个服务从未被调用过。丢弃头信息的 agent 会将一个事务分割成两个不相关的 Trace。对于那些缺乏完善自动插桩的语言(如 C/C++、Go 和 Rust),会导致系统的部分功能完全不可见。像消息队列或后台任务这类异步工作,除非特意进行拼接,否则往往会显示为不连贯的 Trace。
即使管道系统正常工作,实际限制也会悄然出现。采样意味着可能永远无法捕获到确切的失败请求。处于压力下的 Agent 会丢弃 Span。后端会默默截断超过 10,000 个 Span 的跟踪数据。(译者注:这个阈值在有些 APM 方案中可能小到几百)而且,当插桩注入本身出现问题——从错误标记 Span 到占用内存,团队会关闭 Trace 功能,只为保证系统正常运行。最终结果都是一样的:Trace 界面呈现的内容看似完整,实则不然。Trace 是由基础设施汇总的近似信息;而日志才是代码本身的原始记录。正是这种差距,使得经验丰富的工程师将 Trace 视为一种指导,而非绝对真理。
现实世界中 Trace 的常见陷阱
1. 工具的不足
由于未生成任何 Span,导致整个服务或代码路径缺失。
2. 有限的自动插桩
像 Go、Rust 这类语言或自定义框架通常需要大量的手动操作。
3. 异步和非 HTTP 流程
队列、调度器和后台作业会打破跟踪链,除非手动拼接。
4. 损坏的头部传播
不转发跟踪上下文、拆分或截断跟踪的代理、负载均衡器或网关。
5. 不兼容的上下文标准
使用 B3 或专有 Header 且未合并到 W3C 跟踪上下文的旧库。
6. 采样盲点
失败的请求可能根本不会被采集到。
7. Span/Trace 限制
Agent 和后端会限制 Span 数量,默默地丢弃部分事务。
8. Agent 故障
导出队列溢出、Span 丢失,或者有缺陷的工具导致应用程序本身性能下降。
在现实生活中,分布式 Trace 往往只是调查过程中的“中途站”,而非终点。团队接到通知后,会先检查指标以确认影响范围,然后调出 Trace 数据查看哪个服务看起来有问题。但一旦情况不对劲——Span 显示正常、时间数据看起来没问题,或者错误没有清晰呈现——工程师们就会本能地转而查看日志。分布式 Trace 有助于缩小排查范围,但很难直接找到问题根源。这就是为什么在大多数应急会议的复盘总结中,人们对分布式 Trace 的记忆往往是在查看日志的过程中顺便瞥了一眼,而非解决问题的关键工具。
作为一名 SRE,几乎都经历过这样一个“入门仪式”:一条 Trace 信息让你误入歧途。我还没遇到过哪个 SRE 没见过这种情况——请求显示 HTTP 500 错误码,但 Trace 信息却完全没给出原因。真正的问题其实藏在日志里:解析格式错误的负载时出现了空指针异常。
再例举一个我们在某客户项目中遇到的常见场景。
他们运行着一个队列驱动的管道,也遇到了同样的问题。Trace 在消息发布时干净利落地结束了,看起来事务是顺利完成的,但消费者日志却显示作业在不断堆积且悄无声息地失败。在我们最近部署的另一家大型企业中,我们发现,在代理后面的微服务里,缺失的头部传播会将一个请求拆分成两个 Trace,造成下游服务从未被调用的假象,直到其日志揭示了真正的故障。在每种情况下,Trace 都为工程师指明了正确的方向,但正是日志提供了实际解决问题所需的真相。
把 Trace 当作指南,但永远要让日志作为最终定论——相信你的日志,验证你的 Trace。







评论