必须调用 shutdown(),否则导致 goroutine 泄漏、span 丢失;http 中间件需用 httptracecontext 提取/注入 traceparent;客户端 newclient 需包装 transport 或用 otelhttp.newtransport;开发期优先用 stdoutexporter 快速验证 span 结构。

Go 里初始化 TracerProvider 必须调用 Shutdown()
不关 tracer provider 会导致程序退出时 goroutine 泄漏、span 数据丢失,甚至测试里反复 init 出多个 provider 导致 panic。
- 每次启动服务(包括单元测试)只应调用一次
oteltrace.NewTracerProvider(),全局复用 - 务必在
main()退出前或test.T.Cleanup()里显式调用tp.Shutdown(context.Background()) - 若用
otlphttp.NewClient()推送数据,Shutdown()还会触发未发送 span 的 flush,否则最后几百毫秒的 trace 很可能丢掉
HTTP 中间件里提取和注入 trace context 要用 propagators.HttpTraceContext{}
OpenTelemetry 默认不自动解析 traceparent header,手写中间件时容易漏掉 propagation 步骤,结果上下游 trace 断开。
- 接收请求:用
propagators.HttpTraceContext{}.Extract(r.Context(), r.Header)拿到 parent span context - 创建新 span 时传入该 context:
tracer.Start(ctx, "handler"),否则 span 变成 root - 转发请求前:用
propagators.HttpTraceContext{}.Inject(ctx, req.Header)写回 header - 别用自定义 header 名(如
X-Trace-ID),traceparent是 W3C 标准,跨语言兼容性依赖它
otelhttp.NewHandler() 和 otelhttp.NewClient() 的 transport 配置差异
服务端用 NewHandler 默认透传 context,但客户端用 NewClient 不会自动 inject,必须包一层 roundtripper 或手动 propagate。
- 服务端:直接
http.Handle("/api", otelhttp.NewHandler(myMux, "api"))就行,自动 extract + span wrap - 客户端:需用
otelhttp.NewClient(http.DefaultClient)替换 client,它内部 wrap 了 roundtripper 并做 inject/extract - 如果自己构造
http.Transport,记得把otelhttp.NewTransport()套进去,否则 outbound request 没 trace - 注意:gRPC 场景得换
otelgrpc,otelhttp对 grpc over http2 无效
本地开发调试时 stdoutexporter 比 otlphttp 更快定位问题
一上来就配 OTLP endpoint 容易卡在网络、TLS、endpoint 地址拼错上,而 stdout 能立刻看到 span 结构是否正确生成。
立即学习“go语言免费学习笔记(深入)”;
- 先用
stdoutexporter.New()注册 exporter,确认 span name、attributes、parent-child 关系都对 - 再切到
otlphttp.NewClient(otlphttp.WithEndpoint("localhost:4318")),避免把配置错误和逻辑错误混在一起排查 -
stdoutexporter不支持采样控制,上线必须换,但开发期它比看日志或查 collector metrics 直观得多 - 注意:stdout 输出是 JSON 行格式,不是美化后的树状结构,别指望它像 Jaeger UI 那样展示层级
真正难的不是接通 OpenTelemetry,而是让每个 HTTP handler、DB query、goroutine 启动点都持有正确的 context,并在所有异步分支里显式传递——context 丢了,trace 就断了,这点 Go 的静态检查完全帮不上忙。










