新项目必须用 opentelemetry go sdk + jaegerexporter 或 otlphttp,因 jaeger-client-go 已归档、不支持现代 context 模型,导致上下文透传失效、goroutine 中 span 断裂、链路在首跳非 http 调用即消失。

jaeger-client-go 已归档,新项目必须用 OpenTelemetry Go SDK + jaegerexporter 或 otlphttp,否则上下文透传失效、goroutine 中 span 断裂、链路在第一个非 HTTP 跳就消失。
别再初始化 jaeger-client-go tracer
它不支持 Go 的现代 context 模型,StartSpan 返回的 span 无法被子 goroutine 或数据库调用继承。你看到的“单服务链路”或“父子关系错乱”,90% 是这个原因。
-
jaeger-client-go初始化后不校验 reporter 是否连通,失败时返回niltracer,后续StartSpan直接 panic 或静默退化为 noop - 官方已归档(archive 状态),无维护、无 context.Context 集成、不兼容
otelhttp/otelgrpc中间件 - 正确路径:用
go.opentelemetry.io/otel+go.opentelemetry.io/otel/exporters/jaeger或更推荐的go.opentelemetry.io/otel/exporters/otlp/otlphttp - 初始化必须在
main()开头完成:otel.SetTracerProvider(tp),否则所有Tracer().Start()都无效
Gin 入口自动埋点要配对中间件和导出器
只加 otelgin.Middleware 不够,它依赖底层 otelhttp 和全局 TracerProvider。漏掉任一环,span 就卡在入口,DB 和下游 HTTP 调用不会自动挂载。
- 导入:
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin(不是otelgin.NewMiddleware旧版) - 注册中间件前,确保
otel.SetTracerProvider(tp)已执行,且tp包含 Jaeger 或 OTLP 导出器 - DB 调用需显式 wrap:
otelmysql.Wrap(sql.Open(...)),否则 SQL 不计入 span - 下游 HTTP 调用禁用
http.DefaultClient,改用otelhttp.NewClient()实例,它会自动 injecttraceparentheader
跨 goroutine 和异步任务必须手动传 req.Context()
Go 不自动继承 context,go doWork() 这种写法会让子 goroutine 拿不到 parent span,生成孤立 root span——你在 Jaeger UI 看到的“断链”,绝大多数是这里漏了。
- 正确写法:
go doWork(req.Context()),不是go doWork() -
time.AfterFunc回调中,原req.Context()可能已被 cancel,应在回调内重新从 span 获取:ctx := otel.WithSpanContext(context.Background(), span.SpanContext()) - 手动创建子 span 时,始终用
req.Context()作为 parent,禁止用context.Background() - HTTP handler 中调用
span.End()前,别修改w.Header()或写 response body,某些中间件(如 CORS)会提前关闭 writer 导致 panic
gRPC 和 MQ 场景必须手动注入/提取 SpanContext
HTTP header 自动携带 traceparent,但 gRPC metadata 和 AMQP/RabbitMQ message headers 完全不识别它。不显式操作,链路就在第一跳断开。
- gRPC 客户端调用前:
ctx = grpc_md.AppendToOutgoingContext(ctx, "traceparent", traceParentStr),其中traceParentStr来自otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier{}) - gRPC 服务端需启用
otelgrpc.UnaryServerInterceptor,否则 unary 调用不生成子 span - MQ Producer 发送消息时,把当前
SpanContext序列化进消息 headers(如 JSON 字段或 AMQP property);Consumer 收到后先Extract,再Tracer().Start(ctx, ...) - 别指望框架自动做这事——MQ 是黑洞,这一步纯手工,漏掉整个异步链路不可见
context.Context 是否每一跳都准确传递、每个出口是否都正确 inject、每个入口是否都及时 extract。最常被忽略的是 MQ 和定时任务场景,它们没有标准中间件兜底,必须手写透传逻辑。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











