← 返回文章列表
产品动态 4 分钟阅读 炬鲸团队

一个 trace_id 打通日志、指标、链路:炬鲸 OBSERVE 的关联排查

拆解炬鲸 OBSERVE 如何用一个 trace_id 把日志、指标、链路串起来:从告警到根因的完整排查路径,以及让关联真正生效必须做对的三件事。

一个 trace_id 打通日志、指标、链路:炬鲸 OBSERVE 的关联排查

可观测平台用久了会发现一个尴尬:日志、指标、链路三套系统各查各的,一次故障要在三个页面之间来回跳,靠人脑对时间戳。炬鲸 OBSERVE 的做法是把 trace_id 当成唯一的关联键,让三者指向同一次请求。这篇讲清楚这条关联链路是怎么跑通的,以及要让关联真正生效,接入时必须做对的事。

关联的底层逻辑:一个键串三条线

日志、指标、链路是三种不同形态的数据:日志是离散文本,指标是聚合数值,链路是带父子关系的 span 树。要让它们对上号,需要一个共同的键。炬鲸选择 trace_id(没有链路时退化为 request_id)作为这个键:

  • 日志里带上 trace_id 字段,检索时可一键过滤出整条请求的全部日志;
  • span 上记录同样的 trace_id,调用图能定位到具体一跳;
  • 指标打点时把 trace_id 挂到 exemplar 上,从聚合值反查原始样本。

三者共享一个键之后,排查不再是"先猜系统、再翻日志",而是沿键下钻。一个直观的例子:某订单支付超时,你在检索框里敲一句 trace_id = "4bf92f3577b34da6",就把这笔订单经过网关、账户、风控、清算的全部日志按时间顺序拉了出来,同时能看到这条链路的 span 树和每个服务的延迟指标——三块数据不再需要人来"对表"。

没有链路时怎么办:request_id 兜底

不是所有系统都上了链路追踪。对于还没接入 OpenTelemetry 的旧系统,可以用 request_id 或业务单号做同样的关联:日志和指标都带上这个字段,检索时一样能串起一条请求的上下文。等系统逐步迁移到 OTel 后,再把 request_id 统一映射到 trace_id,关联能力无缝升级,不用推翻重来。这一步的价值在于:关联排查不要求"全家桶"一步到位,可以按系统逐个推进。

从告警到根因:一条完整的排查路径

一次典型排障走的是这条路:

  1. 告警触发:p99_latency > 500ms 持续 3 分钟;
  2. 在指标页确认异常维度:是哪个服务、哪个接口在恶化;
  3. 从指标的 exemplar 拿到一个慢请求的 trace_id;
  4. 进链路页看 span 树,定位耗时占比最高的那一跳;
  5. 用同一个 trace_id 过滤日志,直接看到该跳的异常堆栈。

整条路径不换系统、不手动对齐时间戳,MTTR 从小时级压到分钟级。关键在第 3 步:如果指标里没有 exemplar,你就只能回到"凭感觉猜是哪个请求慢了",后面的链路和日志都无从下钻。

让关联生效的配置要点

关联不是默认就有的,接入时这几件事做不对,trace_id 就是断的:

  1. 日志里必须注入 trace_id。Java 用 Logback 的 MDC,通过 OpenTelemetry 的 io.opentelemetry.instrumentation.logback 模块自动注入;Python、Go 要手动在日志格式里加 trace_id 占位符。注入之后要抽查一条线上日志,确认字段真的在,而不是只在本地跑通。
  2. 跨服务传递不能丢。trace_id 靠 HTTP 头(traceparent)或消息头在服务间透传,网关、MQ、定时任务这些"边界"最容易断,要重点检查它们有没有做透传。
  3. 采样策略要一致。链路采样和日志采集如果各采各的,会出现"有 trace 无日志"或反之。建议日志全量、链路按错误率和慢请求自适应采样,保证排障时两者都拿得到。
  4. 时间要对齐。trace 的耗时计算、日志和指标的时间轴都依赖一致的时钟,节点间如果 NTP 漂移,span 的耗时会算出负数,也对不上日志。接入时确认所有节点都开了 NTP。

实践建议

先在一条核心链路上把上面几件事做对,跑通一遍"告警 → trace → 日志"的完整闭环,再推广到全量服务。关联排查的价值只有在真故障里被验证过,才算是真正落地,而不是停留在演示截图里。最直接的衡量标准就一个:上一次真实故障,从告警到确认根因花了多久。