必须在main()开头注册TracerProvider并调用otel.SetTracerProvider和otel.SetTextMapPropagator,否则otel.Tracer()返回nil span,链路静默失效;HTTP须用otelhttp.NewHandler,数据库用otelsql.InjectDriver,goroutine须显式传递context。

otel.Tracer() 返回空 span,不是 SDK 有 bug,而是你没注册 TracerProvider —— 这是 Go 集成 OpenTelemetry 时最常发生的“静默失效”:代码跑得通、不 panic、日志没报错,但 Jaeger / Tempo 里永远看不到 trace_id。
TracerProvider 必须在 main() 开头注册且仅一次
Go 的 OpenTelemetry SDK 不会 fallback 到默认实现。未调用 otel.SetTracerProvider(tp) 时,otel.Tracer("xxx") 永远返回 noop tracer,所有 Start() 调用都静默失败。
- 必须放在
main()函数第一行(或紧随init()后),早于 HTTP server 启动、DB 连接初始化、任何 goroutine 启动 - 绝不能放在
init()函数里:Go 的init执行顺序不可控,HTTP server 可能比 provider 先启动 - 绝不能在 handler 或中间件里临时
new:会导致多个 provider 竞争、exporter 连接泄漏、采样逻辑错乱 - 必须显式传入
resource.WithAttributes(semconv.ServiceNameKey.String("my-service")),否则后端无法按服务分组
示例关键片段:
func main() {
tp, err := initTracer()
if err != nil {
log.Fatal(err)
}
defer func() { _ = tp.Shutdown(context.Background()) }()
<pre class="brush:php;toolbar:false;"><code>http.ListenAndServe(":8080", otelhttp.NewHandler(http.HandlerFunc(handler), "my-service"))}
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
HTTP 入口必须用 otelhttp.NewHandler,不是手写 header 解析
自己写 req.Header.Get("traceparent") + tracer.Start() 看似能跑,实则漏掉三类关键信息:
- 缺失
net.peer.ip、http.status_code、http.route等语义属性 - 无法自动在
WriteHeader后精准结束 span,duration 计算不准 - 跨 goroutine 传播失败:后续异步逻辑拿不到 parent span
otelhttp.NewHandler 是唯一正确绑定 HTTP 生命周期的封装。它已内置 W3C propagator、context 注入/提取、span 命名(如 GET /api/users/:id)和状态码自动记录。
- Gin 用户必须用
ginotel.Middleware(),不能gin.Use(otelhttp.NewHandler()),否则路由参数丢失 - Echo 用户同理,用
echootel.Middleware() - 所有中间件内创建 child span,必须从
r.Context()取 context,严禁用context.Background()
出向调用和异步任务里 context 传错,链路立刻断裂
http.DefaultClient 或原生 http.Client{} 不带任何追踪能力。入向用了 otelhttp.NewHandler,出向还用原始 client,链路只有一跳——下游服务收不到 traceparent,span 直接断裂。
- 正确做法:
client := &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} - 千万别改
http.DefaultClient:会影响其他未 instrument 的库(如某些云 SDK 内部 HTTP 调用) - goroutine 中写
go doWork(context.Background()),等于主动切断链路:新 goroutine 里的tracer.Start()生成 root span,trace_id全 0 - 必须用
trace.ContextWithSpan(ctx, span)注入,而不是context.WithValue(ctx, key, span)—— OpenTelemetry 只认特定 key(trace.ContextKey)
数据库、gRPC、Redis 同理:必须用 otelsql.InjectDriver、otelgrpc.StreamClientInterceptor 等官方插件,手动在 Query() 前后 Start()/End() 只覆盖业务代码执行时间,完全不包含连接池等待、驱动解析、网络往返等真实耗时。
真正卡住人的从来不是“怎么加包”,而是注册时机、context 传递路径、插件使用边界这三处——它们不出错时一切正常,一出错就是整条 trace 彻底消失,连错误提示都没有。

















