默认不生成任何 span,90% 链路在第一跳断裂;根本原因是未配置采样器(默认 sdktrace.neversample)、未设置传播器、未注册导出器,且 otelhttp.newhandler 包裹的 handler 未正确注册进路由。

默认不生成任何 span,90% 的链路会在第一跳就断掉——根本不是代码写错了,而是 sdktrace.NewTracerProvider 没配采样器、没设传播器、也没注册导出器。
为什么 HTTP handler 包了 otelhttp.NewHandler 还没 server span
不是中间件“没生效”,是 handler 根本没被真正注册进路由。常见情况包括:
-
http.Handle("/api", myHandler)写成了原始 handler,正确应为http.Handle("/api", otelhttp.NewHandler(myHandler, "api")) - 用 Gin 时传了裸
http.HandlerFunc,没走gin.WrapH(otelhttp.NewHandler()) -
otelhttp.NewHandler第二个参数(span name)为空字符串,Jaeger 等导出器会静默丢弃该 span - handler 内部 panic 或提前 return,导致
defer span.End()没执行,span 被静默丢弃
采样器必须显式配置,sdktrace.NeverSample() 是默认行为
oteltrace.Tracer 默认使用 sdktrace.NeverSample(),不是“低采样”,是彻底不生成任何 span。现象就是 Jaeger / Tempo 里完全看不到数据,日志也没报错。
- 必须在
sdktrace.NewTracerProvider()中显式传入sdktrace.WithSampler() - 生产环境别用
sdktrace.AlwaysSample()——QPS 上千时 exporter 会积压、重试风暴、拖垮网络 - 推荐配
sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.01)):上游不采样,下游即使想采也收不到traceparent,整条链路消失 - 采样率建议从环境变量读取 + 校验(比如 >1 则设为 0.001),再包一层可变 Sampler 配合信号重载
goroutine 和 DB 调用里 trace 断开的真相
Go 的 context.Context 不跨 goroutine 自动传递,DB 驱动也不读 context 里的 span——这是 Go 分布式追踪最常被忽略的底层事实。
- 启动新 goroutine 时,必须显式传入带 span 的 context:
go doWork(ctx),并在函数内用trace.SpanFromContext(ctx)获取 parent - 对
time.AfterFunc、sync.WaitGroup等异步逻辑,同样要传ctx,并用context.WithTimeout控制生命周期 - DB 查询必须用
db.QueryContext(ctx, ),不能用db.Query();GORM 要调WithContext(ctx) -
context.WithValue在跨进程场景无效,HTTP 出站必须靠propagator.Inject(ctx, carrier)写头,不能靠塞值
导出器连不上 collector 的真实原因
根本原因通常是 exporter 没配对或网络不通,不是代码写错了。OpenTelemetry Go SDK 默认不自动上报,必须显式注册 Exporter 并启动 TracerProvider;漏掉 resource.WithTelemetrySDK() 或忘记调用 tp.Shutdown() 会导致 span 丢弃但无报错。
- 检查
OTEL_EXPORTER_OTLP_ENDPOINT是否指向真实可用的 collector(比如http://localhost:4318/v1/traces),别只写localhost:4318 - 用
stdoutexporter快速验证逻辑:看到 JSON 输出就说明 tracer 初始化成功 - Go 程序退出前必须调用
tp.Shutdown(context.Background()),否则最后一批 span 会静默丢失 - 避免在
init()里初始化全局TracerProvider——单元测试并发时可能 panic
最易被忽略的是 Resource 初始化顺序:必须在 sdktrace.NewTracerProvider 前准备好 resource.MustNewWithAttributes() 并作为选项传入,否则 service.name、host.ip 全丢失,后台无法按服务聚合。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











