新项目必须用opentelemetry go sdk + jaeger导出器替代已归档的jaeger-client-go,初始化tracerprovider须在main()开头完成,http/grpc需标准中间件自动埋点,跨goroutine和db调用必须显式透传context,链路验证优先用stdoutexporter和traceparent头。

别用 jaeger-client-go,它已归档,新项目必须用 OpenTelemetry Go SDK + Jaeger 导出器。否则 context 透传会断、goroutine 中 span 丢失、采样策略不生效——不是“能跑”,而是“看似跑通,实则链路残缺”。
初始化 tracer provider 必须在 main() 开头完成
OpenTelemetry 的 TracerProvider 是全局单例,所有 Tracer().Start() 都依赖它。如果晚于业务逻辑初始化(比如塞在某个 handler 里),所有 span 都退化为 noop,Jaeger UI 里一片空白。
-
otel.SetTracerProvider(tp)要放在main()最早几行,紧随flag.Parse()或配置加载之后 - 导出器优先选
otlphttp.NewClient()对接 Jaeger Collector 的 HTTP 端口(默认14268),避开 UDP 端口(如6831)的丢包和防火墙问题 - 本地调试可临时加
stdoutexporter.NewExporter(),直接看 JSON 输出,比等 UI 刷新快得多
Gin/gRPC 入口必须用标准中间件自动埋点
手动在每个 handler 里 StartSpan 容易漏、难维护,且无法对齐请求生命周期。真正有效的入口埋点靠中间件自动提取 traceparent 并创建 root span。
- Gin:用
otelgin.Middleware(),它自动从req.Header提取并绑定到req.Context(),后续业务代码直接用req.Context()派生子 span - gRPC server:必须注册
otelgrpc.UnaryServerInterceptor,否则 client 发来的 trace 上下文根本不会被识别 - gRPC client:调用前必须套
otelgrpc.UnaryClientInterceptor,否则下游收不到任何 trace header - HTTP client:禁用
http.DefaultClient,改用otelhttp.NewClient()实例,它自动 injecttraceparent
跨 goroutine 和 DB 调用必须显式透传 context
Go 不自动继承 context,90% 的“链路只有一跳”问题都出在这里:goroutine 启动时没传 req.Context(),或 DB 查询没 wrap。
- 异步任务如
go doWork(ctx)——ctx必须是带 span 的req.Context(),不能是context.Background() - MySQL:用
otelmysql.Wrap包装*sql.DB,否则db.Query不计入 span - PostgreSQL:同理,用
otelpostgresql.Wrap - 手动 StartSpan 时,parent 必须来自
req.Context(),即span := tracer.Start(ctx, "db.query"),不是tracer.Start(context.Background(), ...)
验证链路是否完整:先看 stdout,再查 traceparent
上线前别急着配 Collector,先本地直连验证。最快速确认方式不是刷 Jaeger UI,而是看日志和请求头。
- 启用
stdoutexporter,观察输出中是否有嵌套的spans,且traceID一致、parentSpanID正确指向上游 - cURL 请求时加
-v,检查响应头是否含traceparent: 00-...,且该值与你本地生成的 traceID 匹配 - 若服务间调用无
traceparent头,说明 client 端没用otelhttp.NewClient()或 gRPC 拦截器未生效
最容易被忽略的是:Thrift、MQ、定时任务这些非 HTTP/gRPC 场景,它们不走标准 header,必须手动序列化 SpanContext 到自定义字段(如 X-B3-TraceId),否则链路必然在第一个非标准协议节点断裂。











