Go服务接入OpenTelemetry链路跟踪需正确初始化TracerProvider、用otelhttp.NewHandler包裹handler以自动传播span、确保导出器配置无误,并避免context断裂或panic导致span未结束。

Go 服务接入 OpenTelemetry 链路跟踪,核心在于正确初始化 TracerProvider、注入 span 到 HTTP 请求上下文,并确保导出器(如 Jaeger、OTLP)配置无误。多数失败不是因为代码写错,而是 context 传递断裂或导出器未就绪时提前上报。
HTTP handler 中如何自动创建和传播 span
OpenTelemetry Go SDK 不提供开箱即用的中间件,需手动 wrap http.Handler。关键点是:用 otelhttp.NewHandler 包裹原始 handler,它会自动从 inbound request header(如 traceparent)提取 parent span,并在 response 写回 trace context。
- 必须用
otelhttp.NewHandler替代原始http.HandlerFunc,不能只在 handler 内部调用tracer.Start - 若使用 Gin/Echo 等框架,需找对应 otel 插件(如
gin-otel),或手动注入otelhttp.NewHandler到底层http.ServeMux - 注意:
otelhttp.NewHandler默认忽略健康检查路径(如/health),可通过otelhttp.WithFilter显式启用
为什么 span 总是显示为 root,不继承上游 trace_id
常见原因是传入的 http.Request 没有携带 W3C Trace Context 标头(traceparent),或中间件顺序错误导致 context 被覆盖。
- 确认上游调用方(如前端、网关)是否真实发送了
traceparentheader;可用curl -H "traceparent: 00-12345678901234567890123456789012-1234567890123456-01" http://localhost:8080/api手动测试 - 确保
otelhttp.NewHandler是最外层 wrapper;若先经过自定义 auth middleware 再进 otel,且该 middleware 未用req = req.WithContext(...)透传 context,则 span 断链 - 检查是否误用了
context.Background()创建 span —— 应始终用req.Context()作为 parent
Jaeger 和 OTLP 导出器配置差异与选型建议
本地开发常用 Jaeger(UDP/Thrift),生产推荐 OTLP over gRPC/HTTP,两者初始化方式和网络依赖不同。
立即学习“go语言免费学习笔记(深入)”;
- Jaeger 导出器需指定 agent 地址(如
localhost:6831),使用 UDP 协议,不保证送达;启动命令需加--collector.zipkin.host-port=:9411才兼容 Zipkin 格式 - OTLP 导出器默认走 gRPC(
localhost:4317),若防火墙禁用 gRPC,可改用 HTTP/protobuf(http://localhost:4318/v1/traces),此时需用otlphttp.NewClient - 无论哪种,导出器必须在
trace.NewTracerProvider初始化时传入,且TracerProvider需全局复用(通常放init()或 main 入口)
最容易被忽略的是:span 生命周期依赖 defer,而 defer 在 panic 后才执行。如果 handler 内部 panic 且没 recover,span 可能根本没结束,导致 UI 显示 incomplete。务必在顶层 middleware 加 recover() 并显式结束 span。


















