必须调用 shutdown,否则短生命周期服务会丢失最后一批 span;http中间件需最外层注册以确保上下文传递;otlp endpoint协议需与collector端口匹配;获取span为nil主因是context未携带span。

初始化 TracerProvider 时必须调用 Shutdown
不调用 Shutdown 会导致程序退出前丢失最后一批 span,尤其在短生命周期服务(如 CLI 工具、FaaS)里几乎必现。Go 的 TracerProvider 默认使用异步 exporter,内部有缓冲队列和 goroutine,但不会自动等待 flush 完成。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在
main函数退出前显式调用tp.Shutdown(context.Background()),不要依赖 defer(除非确保它在 os.Exit 前执行) - 如果用
http.Server,应在server.Shutdown后再调用tp.Shutdown - 测试中容易漏掉这步——mock exporter 不报错,但真实 jaeger/otlp exporter 会静默丢数据
otelhttp.NewHandler 和 otelhttp.NewClient 的中间件顺序很关键
OpenTelemetry 的 HTTP 中间件不是“插上就生效”,它依赖请求上下文传递 span。常见错误是把 otelhttp.NewHandler 放在日志中间件之后,结果日志里看不到 trace_id;或者 otelhttp.NewClient 包裹了带自定义 RoundTripper 的 client,却没把 span 注入到 header。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
-
otelhttp.NewHandler必须是最外层 handler(即最先被调用),否则下游中间件无法从req.Context()读取 span -
otelhttp.NewClient创建的 client 要直接用于发起请求,不能套在另一个带重试/超时逻辑的 client 外面——重试逻辑若没透传 context,span 就断了 - 手动注入 span 到 outbound 请求?用
propagators.TraceContext{}.Inject(ctx, carrier),别直接写X-B3-TraceId
环境变量 OTEL_EXPORTER_OTLP_ENDPOINT 不生效的典型原因
设了 OTEL_EXPORTER_OTLP_ENDPOINT 却连不上 collector,大概率不是地址写错,而是协议或认证没对齐。OpenTelemetry Go SDK 默认用 gRPC,而很多本地 collector(比如 docker run 的 otel/opentelemetry-collector)默认只开 HTTP 接口(4318)。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 确认 collector 配置:gRPC 端口是
4317,HTTP 是4318;SDK 默认走4317,要改用 HTTP 得显式配置WithEndpoint并指定http://... -
OTEL_EXPORTER_OTLP_PROTOCOL只影响部分老版本 SDK(v1.12 之前),新版本已弃用,靠 endpoint scheme(http://vshttps://)判断协议 - 本地开发建议用
otelcol-dev或直接跑otel/opentelemetry-collector-releases镜像,避免因镜像 tag 混乱导致 exporter 版本不兼容
用 trace.SpanFromContext 获取 span 时返回 nil 的真实场景
不是代码写错了,而是 context 根本没 span。最常发生在:goroutine 分叉后没显式传递 context、HTTP handler 里用了 context.Background()、或第三方库(如 sql.DB.Query)没集成 otel 插件。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 永远用
req.Context()(HTTP)或上游传入的ctx,别自己 newcontext.Background() - 数据库操作要用
go.opentelemetry.io/contrib/instrumentation/database/sql包的Wrap,原生sql.Open不会自动挂 span - 调试时加一行
if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() { ... },比 print 更准——SpanFromContext返回非 nil 不代表有效
链路追踪真正难的不是初始化那几行代码,而是每个 goroutine、每次 http.RoundTrip、每条 sql.Exec 都得主动保 context 通畅。一环断,整条 trace 就碎成段。










