必须在echo.New()之前调用opentracing.SetGlobalTracer(),否则opentracing.StartSpan()因全局tracer为nil触发nil pointer dereference;需先初始化Jaeger tracer并校验非nil,再注册全局tracer,最后创建Echo实例并绑定span到request.Context。

opentracing.SetGlobalTracer() 必须在 echo.New() 之前调用
漏掉这一步是 nil pointer dereference panic 的最常见原因。Echo 中间件或业务代码一旦调用 opentracing.StartSpan(),就会因全局 tracer 为 nil 直接崩溃。
初始化必须放在 main() 最开头,且严格早于 echo.New() 和任何路由注册:
- 先调用
jaeger.NewTracer()获取 tracer 实例 - 立刻传给
opentracing.SetGlobalTracer() - 再创建
e := echo.New() - 最后注册中间件和 handler
如果 tracer 初始化失败(例如 Jaeger Agent 地址不可达),jaeger.NewTracer() 不会 panic,只静默返回 nil —— 务必检查返回值,否则后续所有 span 操作都无效。
中间件里 extract + inject + bind span 到 *http.Request.Context
只调用 tracer.StartSpan() 是不够的。必须把 span 正确注入请求上下文,下游 handler、子中间件、HTTP 客户端才能派生子 span。
关键三步缺一不可:
- 用
opentracing.HTTPHeadersCarrier(r.Header)从 header 提取,key 固定为uber-trace-id(不是X-Trace-ID或traceparent) - 提取失败时新建 root span;成功则用
opentracing.ChildOf(wireCtx)创建 child span - 必须执行
r = r.WithContext(opentracing.ContextWithSpan(r.Context(), span))绑定到 request context
别在 defer span.Finish() 前写响应体或修改 header,某些中间件(如 CORS)会提前关闭 writer,导致 panic。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
HTTP 出向请求必须显式透传 trace header
Echo 自身不处理 outbound 请求的上下文传播。不用额外包装,链路会在第一个外部调用就断裂。
两种可靠方式:
- 用
otelhttp.NewClient()替代原生http.Client,它自动注入uber-trace-id - 若坚持用原生 client,每次发请求前手动调用:
tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) - 发起请求时必须用
http.NewRequestWithContext(r.Context(), ...),确保 outbound request 携带当前 span context
serviceName 和 UDP 地址配置容易被忽略的细节
serviceName 必须是稳定非空字符串,不能带 PID、时间戳或随机后缀。例如用 "user-service",而不是 "user-service-12345" —— 否则 Jaeger UI 里服务名会散列,无法聚合分析。
UDP 地址需可达:
- 本地开发常用
jaeger.NewUDPTransport("localhost:6831", 0) - 确认
jaegertracing/all-in-one正在监听 UDP 6831(默认支持) - Mac 上 Docker Desktop 需加
--network=host,否则容器内 localhost 不指向宿主机
真正难调试的点往往不在代码逻辑,而在 tracer 初始化时机、context 绑定位置、以及出向请求是否真的携带了上下文 —— 这三处任一遗漏,Jaeger 就收不到完整 trace。










