opentracing 在 go 中已归档,新项目必须使用 opentelemetry(otel);其 nil tracer、链路断裂、采样失效等问题均源于与 otel 不兼容的旧生态强行沿用。

opentracing 框架在 Go 中已停止维护且不推荐用于新项目,官方明确归档(2023 年 12 月),所有新链路追踪需求应直接使用 open-telemetry(otel)。
你看到的 nil pointer dereference、采样失效、HTTP 中间件不生效、跨服务 span 断裂等问题,绝大多数都源于强行沿用 opentracing 生态(比如 jaeger-client-go 的旧版封装),而没意识到它和现代 otel 的上下文模型、传播机制、生命周期管理完全不兼容。
为什么 jaeger.NewTracer() 返回 nil 却没人报错?
因为 opentracing.StartSpan 接收的是 opentracing.Tracer 接口,而旧版 jaeger-client-go 的 jaeger.NewTracer() 在 reporter 初始化失败(如 agent 不可达、UDP 端口被占)时会静默返回 nil,但接口调用本身不 panic —— 导致后续所有 StartSpan 都返回 nil span,日志里既无 trace_id,也无错误提示。
- 必须显式检查:
tracer, err := jaeger.NewTracer(...); if err != nil || tracer == nil { log.Fatal(err) } - 别在
init()里调:配置(如agent.host)可能还没从 env 或 config 加载完 -
opentracing.SetGlobalTracer(tracer)后,仍要确保每个 goroutine 都从全局取 tracer,而非缓存变量
HTTP handler 里 span.Context() 总是空,traceparent 头也没用
根本原因是 opentracing 的 HTTP 透传靠手写中间件,但标准库 http.Request 的 context 是只读副本,且 opentracing.HTTPHeadersCarrier 的 Extract/Inject 必须配对使用;漏掉任意一环,链路就断。
- 提取时不能只读
req.Header.Get("uber-trace-id"),得用opentracing.HTTPHeadersCarrier(req.Header)+tracer.Extract() - 注入时不能只改
req.Header.Set(),得用tracer.Inject(span.Context(), opentracing.HTTPHeaders, carrier) - 子 goroutine 必须传
span.Context(),不能用context.Background()或req.Context()(它没继承 span) - 数据库调用(如
db.Query())不会自动读 context,必须手动 Start/End span,无法与 SQL 生命周期对齐
采样率设了却全量上报,Jaeger UI 里全是毛刺
opentracing 本身不提供采样器抽象,采样逻辑全压在 client 实现里(如 Jaeger 的 ProbabilisticSampler),但它的决策时机在 span 创建前,且无法动态更新 —— 一旦初始化完成,再改配置也没用。
- 采样率必须在
jaeger.NewTracer()时通过jaeger.WithSampler(...)传入,不能运行时修改 -
ProbabilisticSampler(0.01)是按 trace ID 哈希后概率采,不是“每 100 个请求采 1 个”,低频服务可能数小时无数据 - 没有
ParentBased机制:下游服务无法继承上游的采样决策,导致父子 span 采样不一致,UI 上出现“半截链路” - UDP exporter 默认不压缩,容器内易触发 MTU 截断(尤其 span 属性多时),建议切到 HTTP exporter
想共存 OpenTelemetry 和 OpenTracing?别试了
两者对 service.name、operation.name、span.kind 的语义定义不同,opentracing 的 StartSpan 生成的 span 无法被 otel 的 propagator 正确解析,反之亦然。强行桥接(如 ot/opentracing 适配层)会导致:
- traceparent 头被忽略,降级为
uber-trace-id,下游otel服务无法识别 - span 属性丢失(如
http.status_code、net.peer.ip) - Resource(服务元信息)不统一,Jaeger/Tempo 无法按 service 维度聚合
- 最终结果是:数据进了后端,但查不到、分不了、关联不上
真正可行的路径只有一条:停用所有 opentracing 相关 import,删掉 jaeger-client-go,全部迁移到 go.opentelemetry.io/otel,用 otelhttp.NewHandler 和 otelhttp.NewTransport 替代手写中间件。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











