otel.tracer 返回空 span 的根本原因是 otel.settracerprovider(tp) 未执行或执行过晚,导致 sdk 静默使用 noop tracer;必须在 main 函数开头初始化含有效 exporter 的 tracerprovider,并确保所有埋点(http、db、goroutine)均正确传递 context。

为什么 otel.Tracer 返回空 span?
绝大多数“链路没数据”问题,根源是 otel.SetTracerProvider(tp) 没执行,或执行得太晚。OpenTelemetry Go SDK 不校验、不报错、不 panic,只静默返回 noop tracer —— 你调 tracer.Start() 看似成功,实际什么都没发出去。
必须在 main() 函数最开头完成初始化,且 tp 必须包含有效 exporter(比如 jaegerexporter.New 或 otlphttp.NewExporter):
func main() {
// ✅ 必须最先做
tp := trace.NewTracerProvider(
trace.WithBatcher(exporter), // exporter 已配置好
)
otel.SetTracerProvider(tp)
defer func() { _ = tp.Shutdown(context.Background()) }()
// ❌ 这里再初始化 Gin、HTTP server 等
}
-
otel.Tracer("my-service")本身不触发任何初始化,它只是从全局TracerProvider拿实例 - 如果
otel.SetTracerProvider漏掉或晚于第一个Tracer().Start()调用,后续所有 span 都是空壳 - 本地开发建议先用
stdouttrace.NewExporter(),避免因 OTLP collector 不通而误判代码问题
HTTP handler 怎么自动埋点又不断链?
别自己解析 traceparent header,也别只加中间件却不配导出器。Gin 场景下,三要素缺一不可:otel.SetTracerProvider + otelgin.Middleware + 对应的 exporter。
导入和注册要严格匹配:
import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
r := gin.Default()
r.Use(otelgin.Middleware("my-api")) // 注意:不是 otelgin.NewMiddleware
-
otelgin.Middleware依赖全局TracerProvider,它只负责提取 context 并创建入口 span,不负责发送 - DB 调用不会被自动捕获,必须显式 wrap:
db, _ := otelmysql.Wrap(sql.Open("mysql", dsn)) - 下游 HTTP 请求必须用
otelhttp.NewClient()实例,禁用http.DefaultClient;发起前记得req = req.WithContext(ctx)
goroutine 和异步回调里 span 为什么总是断?
Go 不自动跨 goroutine 传递 context,go doWork() 这种写法必然生成孤立 root span —— Jaeger 里看到的“断链”,90% 是这里漏了。
正确做法永远是显式传带 span 的 context:
// ✅ 正确:把 handler 的 ctx 传进去
go doWork(req.Context())
// ❌ 错误:丢掉 parent span
go doWork()
// ✅ time.AfterFunc 场景:原 ctx 可能已 cancel,改用 span 自身上下文
spanCtx := span.SpanContext()
go func() {
ctx := otel.WithSpanContext(context.Background(), spanCtx)
doWork(ctx)
}()
- 禁止在子 goroutine 中用
context.Background()创建 span - 第三方库(如 ent、gorm 旧版)若未适配 OTel,其 hook 回调里
req.Context()可能为空,需手动 inject:otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier{...}) - 结构化日志注入
trace_id时,必须从 context 提取:tid := trace.SpanFromContext(ctx).SpanContext().TraceID().String(),不能硬编码 header 名或靠ctx.Value("trace_id")
新项目还能用 jaeger-client-go 吗?
不能。该库已归档,不支持 Go 的现代 context.Context 模型,会导致上下文透传失效、goroutine 中 span 断裂、非 HTTP 首跳即消失 —— 你看到的“单服务链路”或“父子关系错乱”,基本就是它。
替代路径只有两条:
-
go.opentelemetry.io/otel/exporters/jaeger:兼容现有 Jaeger backend,但需用 OTel SDK 初始化 -
go.opentelemetry.io/otel/exporters/otlp/otlphttp:更推荐,直接对接 OTLP collector,生态更统一
别再初始化 jaeger.NewTracer 或调 opentracing.SetGlobalTracer,这些调用在 OTel 环境下完全无效,还可能干扰上下文传播。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











