应改用 opentelemetry + jaegerexporter,因 jaeger-client-go 已归档;init() 初始化 tracer 常失败是因配置未加载完导致 otel.gettracerprovider() 返回 nil;必须在 main() 开头显式调用 inittracer() 并校验 provider 非空。

jaeger-client-go 已归档,别再用它初始化 tracer。
现在标准路径是 otel + jaegerexporter,否则上线后 Span 会断链、UI 显示为空、CI 构建失败——不是配不配得上,是根本跑不起来。
为什么 init() 里初始化 tracer 总失败
常见现象:otel.GetTracerProvider() 返回 nil,或 tp.Shutdown panic;本质是配置还没加载完就硬初始化。
- 环境变量、配置文件读取通常在
main()里才开始,init()阶段拿不到JAEGER_AGENT_HOST等值,导致jaeger.New()内部 reporter 构造失败 -
otel.SetTracerProvider(tp)必须在所有业务逻辑前调用,但不能早于配置就绪——建议封装成initTracer() error函数,在main()开头显式调用 - 本地调试时加一句
log.Printf("tracer provider: %v", otel.GetTracerProvider()),确认不是<nil></nil>
Gin 中间件里 traceID 传不下去的典型表现
Jaeger UI 里只看到单层 Span,下游服务完全无数据;或者 span.Context() 返回空 —— 不是代码没写,是 context 没绑定对。
- 必须用
otel.GetTextMapPropagator().Extract(req.Context(), propagation.HeaderCarrier(req.Header))提取上游上下文,别手动读X-B3-TraceId或uber-trace-id - 创建新 span 后,得用
req = req.WithContext(ctx)把带 span 的 context 注入 request,再交给 next handler - 中间件顺序不能错:trace 中间件必须在 logger、auth、recovery 等之前,否则这些中间件拿不到有效 span
- 别漏掉
w.WriteHeader()前就span.End(),某些 Gin 中间件(如gin.Recovery())会在 panic 后重写 header,触发 writer closed panic
UDP 上报静默丢包,但日志里啥也不报
最坑的是:没数据、没错误、没 warning —— jaeger.WithAgentEndpoint() 默认用 UDP,连不上 agent 就直接丢弃。
- Docker 环境下,
WithAgentHost("localhost")是错的:容器内 localhost 指自己,得换成jaeger-agent(docker-compose service 名) - 确认 jaeger-all-in-one 容器暴露了
6831/udp:启动命令必须含-p 6831:6831/udp,且镜像版本 ≥ v1.30(旧版默认监听 6832) - 开发阶段优先用
jaeger.WithCollectorEndpoint()+ HTTP,端口14268,失败会明确返回 error,方便定位 - 生产环境若坚持 UDP,加
jaeger.WithAgentTimeout(1 * time.Second)控制阻塞时间,避免卡住 goroutine
Span 断在 gRPC 或 MQ 调用之后
HTTP → gRPC → RabbitMQ → 消费者,这条链里后两跳最容易丢 trace —— 因为它们不走 HTTP header 透传。
- gRPC 客户端调用前,必须用
grpc_md.AppendToOutgoingContext(ctx, "uber-trace-id", ...)注入;服务端用grpc_md.ExtractIncoming(ctx)提取 - RabbitMQ Producer 发送消息时,要把
span.SpanContext()序列化进 message headers(如amqp.Table{"trace-id": "..."}) - Consumer 收到后,用
otel.GetTextMapPropagator().Inject()构造 carrier,再调otel.Tracer("mq").Start() - 别依赖框架自动注入:Gin、Echo 自动处理 HTTP,但 gRPC、AMQP、Redis client 都得手动补
关键点不在“有没有 tracer”,而在“context 是否全程在线”——Span 断掉的地方,90% 是某次函数调用没把 ctx 传进去,或用了新生成的 context.Background()。上线前用 Jaeger UI 点开任意 trace,看每层 span 的 trace_id 和 parent_id 是否连续,比写十遍 demo 都管用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











