必须改用OpenTelemetry,因jaeger-client-go已于2024年底归档,不支持Go原生context透传,易致span断裂、链路缺失且静默失效;新项目应使用go.opentelemetry.io/otel及配套导出器与中间件,并在main()首行初始化TracerProvider。

jaeger-client-go 已归档,别再初始化它
新项目直接用 go.opentelemetry.io/otel + go.opentelemetry.io/otel/exporters/jaeger,jaeger-client-go 在 2024 年底已归档,不支持 Go 原生 context.Context 透传模型。你看到的“链路只有一跳”“父子 span 错乱”“goroutine 里 span 消失”,90% 是因为还在用它。
它初始化失败时不会报错,而是静默返回 nil tracer;后续所有 StartSpan 都退化为 noop,日志没提示、HTTP 状态码正常、Jaeger UI 空空如也——排查成本极高。
-
opentracing.Tracer→ 全部替换为otel.Tracer -
cfg.NewTracer()→ 改为sdktrace.NewTracerProvider()+otel.SetTracerProvider() -
uber-trace-idheader → 必须用标准traceparent(W3C Trace Context)
TracerProvider 初始化必须在 main() 开头执行
所有 Tracer().Start() 调用都依赖全局 provider。如果 otel.SetTracerProvider(tp) 写在 HTTP server 启动之后,或被包裹在某个 init 函数里延迟执行,那前几个请求的 span 全是 noop —— Jaeger 里看不到数据,也不报错。
正确顺序只有一条:先初始化,再启动服务。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- 写死在
func main()第一行,不要封装进条件分支或 defer - 确保
tp包含有效导出器(如jaeger.New()或otlphttp.NewClient()),否则 provider 无效 - 别漏掉
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})),否则traceparent头无法自动解析和注入
Gin/gRPC/HTTP 客户端必须配对中间件与导出器
只加 otelgin.Middleware("my-service") 不够。它依赖底层 otelhttp 和已设置的全局 TracerProvider。漏掉任一环,span 就卡在入口,下游 DB 和 HTTP 调用不会自动挂载。
- Gin:导入
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin(不是旧版otelgin.NewMiddleware),注册前确认otel.SetTracerProvider(tp)已执行 - gRPC:服务端必须启用
otelgrpc.UnaryServerInterceptor,客户端必须用otelgrpc.UnaryClientInterceptor,否则 unary 调用无子 span - HTTP 客户端:禁用
http.DefaultClient,改用otelhttp.NewClient()实例,它会自动 injecttraceparentheader
跨 goroutine 和 DB 调用必须显式透传 context
Go 不自动继承 context.Context。写成 go doWork() 而非 go doWork(req.Context()),子 goroutine 就拿不到 parent span,生成孤立 root span——你在 Jaeger UI 看到的“断链”,绝大多数是这里漏了。
- 异步任务(如
time.AfterFunc)中,原req.Context()可能已被 cancel,应在回调内重新构造:ctx := otel.WithSpanContext(context.Background(), span.SpanContext()) - DB 调用必须 wrap:
otelmysql.Wrap(sql.Open(...))或otelpostgresql.Wrap(...),否则 SQL 执行不计入 span - 手动创建子 span 时,parent 必须来自
req.Context(),禁止用context.Background()
最常被忽略的点:span 生命周期必须严格对齐请求生命周期。handler 中调用 span.End() 前,别提前修改 w.Header() 或写 response body,否则 span 可能被提前关闭,丢失结束时间与状态码标签。

















