← 返回文章列表
使用指南 4 分钟阅读 炬鲸团队

用 trace_id 打通日志与链路:OpenTelemetry 日志关联实战

排查超时通常要三步:看慢请求、定位 span、回日志找报错。如果日志里没有 trace_id,第三步就是大海捞针。这篇讲如何用 OpenTelemetry 把日志和链路串起来,含异步与网关场景的踩坑。

用 trace_id 打通日志与链路:OpenTelemetry 日志关联实战

排查一次超时,通常要经历三步:先看到慢请求,再顺着链路定位到具体 span,最后去日志里找这个 span 到底报了什么错。如果日志里没有 trace_id,第三步就是大海捞针。这篇讲怎么用 OpenTelemetry 把日志和链路串起来。

关联的核心:把 trace_id 写进每行日志

思路不复杂:应用产生 trace 时,把当前的 trace_id 和 span_id 注入到日志上下文,日志库输出时自动带上这两个字段。以 Python 的 opentelemetry-instrumentation-logging 为例:

import logging
from opentelemetry.instrumentation.logging import LoggingInstrumentor

LoggingInstrumentor().instrument(set_logging_format=True)
logger = logging.getLogger(__name__)

做完这一步,日志输出会自动带上 otelTraceID 和 otelSpanID。Java 用 opentelemetry-logback-appender,在 logback.xml 里挂上 appender 即可;Go 在 logrus 或 zap 的 Formatter 里加两个字段就行。三者的共同点是都不需要改业务代码,只在日志框架接入层做一次配置。

关键不是埋点,而是把这两个字段定义成结构化字段,别拼进 message 字符串里。拼进去的话,检索只能靠正则,代价高还不稳定;结构化字段能建索引、能精确过滤。

让日志也能形成 trace

OpenTelemetry 的 Logs 信号和 Metrics 一样,可以带 trace_id 和 span_id 属性。配置 OTLP exporter 时把日志和 trace 发到同一个后端,后端就能把日志挂到对应的 span 上:

exporter:
  otlp:
    endpoint: "ob.example.com:4317"
    protocol: grpc

炬鲸 OBSERVE 会按 trace_id 做索引,在链路详情页直接列出该请求产生的所有日志。点开一个慢 span,下面就是它打出来的 ERROR,不用再切到日志页猜关键词。检索接口也支持按 trace_id 精确过滤,一个请求的全部日志一句话就能捞出来。

验证是否生效很简单:先制造一个会打日志的请求,拿到 trace_id,再到日志检索里按 trace_id 过滤,看能不能捞出这个请求打的那几条日志;再回到链路详情页点开对应 span,确认日志面板里有内容。两头都通,关联才算跑通。

常见坑:异步任务和跨线程丢了 trace_id

日志关联最容易翻车的地方是异步。线程池、消息队列、定时任务里,上下文已经切了,trace_id 跟着上下文一起丢,日志就关联不上。

两个做法:一是用 OpenTelemetry 的 Context 显式传递,Context.attach() 把父 span 的上下文带进新线程;二是在队列消息里手动带上 trace_id,消费端再 set_span_in_context 恢复。别指望线程本地变量自动跨线程,它做不到。

另一个坑是网关。如果请求经过 Nginx、Kong 或网关,记得把上游生成的 trace_id 透传给下游,否则 gateway 的 span 和应用的 span 是两棵树。透传头就是标准 W3C 的 traceparent,网关侧别把它当普通 header 过滤掉。

还要注意采样。如果只保留头部采样,出错的那几条 trace 可能刚好被采样掉,日志也就关联不上关键现场。建议对错误和高延迟请求做尾部采样,保证慢请求和报错请求的完整链路 + 日志都留得下来。

排查:日志里还是没有 trace_id

如果按上面配完,日志里还是没出现 trace_id,按顺序查这几处:一是埋点是否真的生效,看应用启动时有没有 OpenTelemetry 的初始化日志;二是日志格式,确认 Formatter 里加了 otelTraceID 字段;三是 exporter 是否同时配了 logs 和 traces 两条 pipeline,只发 trace 不发 logs 是常见漏配;四是排查采样,头部采样把这条 trace 采掉了,日志自然没有关联对象。逐条过一遍,基本都能定位。

落地建议

  • 统一用 OTel 的 Logs 信号,别各语言自己拼格式;
  • trace_id 作为结构化字段,索引必须建;
  • 异步链路显式传上下文,队列消息里带 trace_id;
  • 先在一条核心服务上跑通,再推广到全站。

打通日志和链路之后,一次故障从"猜"变成"点两下就能看到"。这笔投入比加机器划算得多。