jaeger agent 端口配错导致 span 静默丢失:jaeger-all-in-one 默认监听 6832/udp,非旧版 6831;配错则 span 直接丢弃且无报错;需用 netstat 验证或改配 6832;直连 collector 应用 http reporter(端口 14268)并启用 logspans;gin 中间件须显式提取、创建、绑定 span 到 context;grpc 调用需手动 inject/extract httpheaders 格式上下文;mq 场景需业务层序列化 spancontext 到消息体;定时任务等非请求入口需人工补全初始化。

Jaeger Agent 端口配错导致 Span 静默丢失
本地开发时最常踩的坑:启动了 jaeger-all-in-one,但代码里仍写 LocalAgentHostPort: "localhost:6831"。实际上,jaeger-all-in-one 默认监听的是 6832/udp(不是 6831),而 6831 是旧版 Agent 的默认端口。配错后 Span 会直接丢弃,控制台无报错、Jaeger UI 里完全没数据。
验证方式很简单:netstat -anu | grep 683 看哪个 UDP 端口在 LISTEN;或者直接改配置为 "localhost:6832" 再试。
- 用
docker run -p 6832:6832/udp ... jaegertracing/all-in-one启动时,必须配6832 - 若跳过 Agent 直连 Collector,得换用 HTTP reporter,端口是
14268,且要配jaeger.NewRemoteReporter+transport.NewHTTPTransport -
Reporter.LogSpans: true打开后,控制台会打印每条 Span,适合快速确认是否生成/上报成功
Gin 中间件里 Span 绑定 Context 必须显式传递
只调用 tracer.StartSpan 不够,Gin 的 c.Request.Context() 默认不包含 Span。如果不手动注入,后续业务逻辑、子 Span 创建、下游 HTTP/gRPC 调用都无法继承 trace 上下文,链路会在第一层就断掉。
正确做法是在中间件中完成提取、创建、绑定三步:
- 用
tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))尝试从请求头提取上游 SpanContext - 失败时新建 root span:
span := tracer.StartSpan("http-server") - 必须用
opentracing.ContextWithSpan(r.Context(), span)构造新 context,并通过c.Request = r.WithContext(newCtx)注入进去 - 最后
defer span.Finish(),注意别在它之前调用c.Writer.WriteHeader()或写 body,某些 Gin 中间件会 panic
gRPC 和 HTTP 混合调用时 Context 透传不能靠自动
HTTP 请求能靠 header 自动携带 uber-trace-id,但 gRPC metadata 不识别这个 key。如果服务 A(Gin)调用服务 B(gRPC),不手动 inject,B 收到的就是孤立 root span。
关键动作两处都要做:
- 客户端侧:拿到当前 span 的 context 后,用
tracer.Inject(span.Context(), opentracing.HTTPHeaders, carrier),再把 carrier 中的 headers 转成 gRPCmetadata.MD传过去 - 服务端侧:收到 metadata 后,用
tracer.Extract(opentracing.HTTPHeaders, carrier)提取,再调用tracer.StartSpan("rpc-handler", opentracing.ChildOf(extractedSpanCtx)) - 别依赖第三方 gRPC middleware(如
grpc_opentracing),它可能不兼容新版 OpenTracing API 或已弃用
MQ 场景下 SpanContext 必须序列化进消息体
RabbitMQ/Kafka 本身不传上下文,Producer 发消息时不塞 traceID,Consumer 收到后起的 Span 就是全新 root,整条链路在 MQ 处断裂。
解决方式不是加中间件,而是业务层改造:
- Producer 发送前,用
span.Context().(jaeger.SpanContext).TraceID().String()提取 traceID,写入消息 header 或 payload 字段(如X-Trace-ID) - Consumer 收到后,手动构造
jaeger.SpanContext,填入 traceID/spanID 等字段,再调用tracer.StartSpan(..., opentracing.ChildOf(spanCtx)) - 不要用 Baggage 传业务数据——它不参与 traceID 生成,仅作键值透传,且部分 exporter 会丢弃
跨协议链路最容易被忽略的其实是 MQ 和定时任务这两类“非请求驱动”入口,它们没有天然的 request context,必须靠人工补全 SpanContext 初始化逻辑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











