新项目必须用opentelemetry go sdk + jaeger导出器,因jaeger-client-go已于2024年底归档,不维护、context透传失效、span静默断裂且不兼容w3c traceparent标准。

直接上结论:新项目必须用 OpenTelemetry Go SDK + Jaeger 导出器,jaeger-client-go 已归档、不维护、链路会静默断裂——不是报错,而是根本看不到 trace。
为什么 jaeger-client-go 不能用了
它在 2024 年底正式归档,OpenTelemetry 已成 CNCF 标准。继续用会导致:
-
tracer.StartSpan()看似成功,但实际返回的是 noop span,Jaeger UI 里查不到任何 trace - 跨 goroutine 调用时 context 无法透传,子 goroutine 的 span 自动丢失
- 采样策略(如
ProbabilisticSampler)完全不生效,所有请求都走默认 1:1 上报或全丢 - HTTP header 提取只认
uber-trace-id,不兼容 W3Ctraceparent标准,和现代网关/Service Mesh 不互通
otelhttp + otelgrpc 中间件怎么配才不漏 span
Echo 本身没官方 OpenTelemetry 中间件,得靠 otelhttp 的 handler 包装 + 手动绑定 context。关键点是:root span 必须从 request.Context 提取并注入,不能新建。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 入口中间件必须调用
otelhttp.NewHandler()包装 Echo 的HTTPErrorHandler或自定义 handler,而不是自己写StartSpan - 提取失败时(比如 header 为空),要用
otel.GetTextMapPropagator().Extract()+otel.SpanContextFromContext()判断是否 fallback 到 root span,不能无条件Start() - 每个 handler 执行前,必须把 span 绑定回
c.Request().WithContext(),否则下游c.Get("span")或业务层tracer.Start(ctx, ...)拿不到 parent - 别在中间件里 defer
span.End()—— 如果后续中间件 panic 或 writer 已关闭,会触发 double-end 或 panic
TracerProvider 初始化顺序和导出器选型
初始化晚于业务代码 = 全链路失效。导出器选错 = 数据发不出去或被静默丢弃。
-
sdktrace.NewTracerProvider()和otel.SetTracerProvider()必须放在main()最开头,紧随flag.Parse()或配置加载之后,早于echo.New() - 本地开发优先用
stdoutexporter.NewExporter(),直接看 JSON 输出,比等 Jaeger UI 刷新快得多 - 对接 Jaeger 时,导出器别用 UDP 端口(如
localhost:6831),改用 HTTP Collector 端口:otlphttp.NewClient(otlphttp.WithEndpoint("localhost:14268")) - 如果用
jaeger.NewUnstartedExporter(),必须显式调exp.Start(),否则数据卡在内存里不上报
HTTP 客户端调用下游服务时 trace 为什么断了
不是 Echo 的问题,是 Go 的 http.Client 默认不注入 trace header。手动拼 header 容易漏、难维护、不兼容 W3C。
- 禁用
http.DefaultClient,改用otelhttp.NewClient()实例,它自动 injecttraceparent - 如果你用
rk-boot,直接调rkechoctx.InjectSpanToHttpRequest(c, req),它内部已处理好 propagation - 下游是 gRPC?客户端必须套
otelgrpc.UnaryClientInterceptor(),服务端注册otelgrpc.UnaryServerInterceptor(),缺一不可 - DB 调用也要 wrap:
otelmysql.Wrap(db)或otelpostgresql.Wrap(db),否则 SQL 查询不会出现在 span 生命周期里
最容易被忽略的其实是 context 透传时机:goroutine 启动、defer 函数、异步回调、DB 查询,这些地方只要用了 context.Background() 或没显式传入带 span 的 ctx,链路就断在那一跳——而且没有任何错误提示,只有 Jaeger 里 trace 长度变短。










