根本原因是未调用otel.SetTracerProvider或其传入的TracerProvider初始化失败,导致otel.Tracer()返回noop实现,所有span静默失效、无trace_id、无日志报错;必须在main()开头一次性完成TracerProvider注册、传播器设置和导出器配置。

otel.Tracer 返回空 span?链路在第一跳就断?不是代码写错了,是 OpenTelemetry Go SDK 默认不生成任何 span——它压根没启动。
必须在 main() 最开头完成三件事:注册 TracerProvider、设置传播器、指定导出器。漏掉任意一项,后续所有 tracer.Start() 都静默失效,Jaeger / Tempo 里查不到 trace_id,日志也不报错。
为什么 otel.Tracer() 总是返回 nil
根本原因只有一个:otel.SetTracerProvider() 没调,或调了但传入的 TracerProvider 初始化失败(比如 exporter 地址不通,SDK 不报 panic,只静默 fallback 到 noop 实现)。
- 别在
init()里初始化——Go 的init执行顺序不可控,HTTP server 可能比 tracer provider 先 ready - 别在 handler 或中间件里临时 new
sdktrace.NewTracerProvider()——会导致多个 provider 竞争、exporter 连接泄漏、采样逻辑混乱 - 必须显式传入
resource.Resource,至少带service.name属性,否则 Jaeger 无法按服务分组 - 本地调试优先用
stdoutexporter.NewUnstarted()+.Start(),终端直接看到 JSON 格式 span,比查日志快得多
otelhttp.NewHandler 包裹后还是没 server span
常见现象:HTTP 请求带了 traceparent,但下游服务收不到,所有 span 都是独立 root span。本质是 propagation 没配对,或中间件没真正注册进路由。
-
otelhttp.NewHandler必须包裹最终业务 handler,不是中间件链里的某一层;例如 Gin 中不能混用otelhttp.NewHandler和otelgin.Middleware,否则双 span - 必须调用
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.B3{})),兼容新老系统 -
otelhttp.NewHandler第二个参数(span name)不能为空字符串,Jaeger 会静默丢弃 - handler 内部 panic 或提前 return,导致
defer span.End()没执行,span 被静默丢弃
goroutine 和 DB 调用里 trace 断开的真相
Go 的 context.Context 不跨 goroutine 自动传递,DB 驱动也不读 context 里的 span——这是最常被忽略的底层事实。
- 启动新 goroutine 时,必须显式传入带 span 的 context:
go doWork(trace.ContextWithSpan(ctx, span)),不能只传ctx或用context.Background() -
db.Query()前后手动tracer.Start()/End()是典型误区:它只覆盖业务代码执行时间,不包含连接池等待、驱动解析、网络往返 - 正确做法是用
go.opentelemetry.io/contrib/instrumentation/database/sql,注册时调sql.OpenDB(otel.WrapDriver(driver, &otelsql.Config{})) -
time.AfterFunc、sync.WaitGroup等异步逻辑同样要传 ctx,并用context.WithTimeout控制生命周期
采样器没配,链路全丢
sdktrace.NeverSample() 是默认行为,不是“低采样”,是彻底不生成任何 span。现象就是完全看不到数据,且无任何错误提示。
立即学习“go语言免费学习笔记(深入)”;
- 必须在
sdktrace.NewTracerProvider()中显式传入sdktrace.WithSampler() - 生产环境别用
sdktrace.AlwaysSample()——QPS 上千时 exporter 会积压、重试风暴、拖垮网络 - 推荐配
sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.01)):上游不采样,下游即使想采也收不到traceparent,整条链路消失 - 采样率建议从环境变量读取 + 校验(比如 >1 则设为 0.001),再包一层可变 Sampler 配合信号重载


















