生产环境应只用 opentelemetry go sdk,因 opentracing-go 和 jaeger-client-go 存在设计范式淘汰、上下文透传易丢失、标准不兼容等问题;otelhttp/otelgrpc 需正确配置传播器、中间件顺序及 context 替换;span 必须显式 end、用含 parent 的 ctx 创建、异步任务需手动携带 span。

生产环境只用 OpenTelemetry Go SDK(go.opentelemetry.io/otel),其他方案要么已废弃,要么埋坑。 opentracing-go、jaeger-client-go、OpenCensus 等旧库在 Span 生命周期管理、上下文透传一致性、采样策略统一性上存在不可忽视的缺陷,升级成本远高于初期选型成本。
为什么 opentracing-go 和 jaeger-client-go 不再推荐
这两个库的问题不是“不够好”,而是设计范式已被淘汰:
-
opentracing-go是接口抽象层,不提供实现,需自行绑定 tracer(如 jaeger-client-go),导致传播器、采样器、导出器配置分散,容易漏配 -
jaeger-client-go的Tracer初始化依赖init()或全局变量,与 Go 模块初始化顺序冲突,常导致日志、指标等依赖未就绪时 tracer 已启动 - 父子 Span 关系靠手动传
context+StartSpanFromContext,但该函数在跨 goroutine 或异步调用中极易丢失上下文,且不校验 parent span 是否已结束 - HTTP 头注入/提取默认用
X-Jaeger-*,与 W3Ctraceparent或 B3 标准不兼容,对接现代 collector(如 OTel Collector)需额外桥接
otelhttp 与 otelgrpc 中间件怎么用才不出错
自动 instrumentation 不等于“零配置”,关键点在于传播器必须提前设置、中间件顺序不能错、handler 必须用新 context:
- 必须在
http.ServeMux或 Gin/Echo 启动前调用otel.SetTextMapPropagator(propagation.TraceContext{}),否则otelhttp.NewHandler无法读取 trace header - Gin 场景下:注册中间件顺序应为
recovery → tracing → your handlers;中间件内必须用c.Request = c.Request.WithContext(extractTraceFromRequest(c.Request))替换原 request,不能只调c.Request.Context() -
otelhttp.NewClient返回的 client 能自动 inject,但若你用原始http.DefaultClient或自定义 transport,则必须手动调用injectTraceToRequest(ctx, req) - gRPC 场景禁止手写
metadata.MD透传——直接用otelgrpc.UnaryClientInterceptor()和otelgrpc.UnaryServerInterceptor()
Span 生命周期管理最容易踩的三个坑
Go 的 defer + context 组合让 Span 泄漏和父子错乱特别隐蔽:
- Span 必须显式
span.End(),且必须在 defer 中执行:defer span.End();写成span.End(); defer func(){}会导致立即结束,子 span 无 parent - 创建子 span 时,必须用
tracer.Start(ctx, "name"),其中ctx是从上游 extract 出来的、含 parent span 的 context;用context.Background()就等于断链 - 异步任务(如 goroutine、定时回调)中使用 span 前,必须用
context.WithValue(ctx, key, span)显式携带,Go runtime 不会自动跨 goroutine 传递 span
真正难的不是接入,是让每个 HTTP handler、每个 goroutine、每个数据库查询都处在同一 trace 上下文里。传播器设错、defer 写反、context 忘传——任何一个环节松动,整条链路就碎成几段。别指望“自动”能兜住所有场景。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











