go链路追踪静默失效的根本原因是tracerprovider未在main()开头注册且未调用otel.settracerprovider和otel.settextmappropagator,导致otel.tracer()返回nil span,所有埋点失效、jaeger无数据。

Go 环境装好了不代表链路追踪能跑起来;otel.Tracer 不 panic、span 为 nil、Jaeger 里空空如也——这八成是环境和 SDK 初始化没对上,不是代码写错了。
go 命令可用但 otel 不生效:PATH 和模块模式先过两关
很多人的 go version 能跑,但 go get go.opentelemetry.io/otel 报错或依赖不生效,根源在模块模式没开或 GOPATH 干扰。
- 运行
go env -w GO111MODULE=on强制启用模块(Go 1.16+ 默认开启,但 CI 或旧脚本可能关着) - 删掉项目根目录下的
vendor/目录(除非你明确要 vendor) - 别在
$GOPATH/src下新建项目——go mod init会误推 module 名,导致 import 路径错乱 - 验证
go是否真在 PATH:macOS/Linux 执行which go,Windows 用 PowerShell 运行Get-Command go
TracerProvider 必须在 main() 第一行注册,且不能是 init()
otel.Tracer("x") 永远不会 panic,但若没提前设好 TracerProvider,它返回的 span 就是 nil,所有埋点静默失效——HTTP handler、DB 查询、goroutine 全部断链,日志里连 warning 都没有。
- 必须在
main()函数第一行(或紧随init()后)调用初始化函数,例如initTracer() - 严禁在
init()里初始化:配置(如JAEGER_AGENT_HOST)还没加载,jaeger.NewTracer()可能返回nil,otel.SetTracerProvider(tp)传入nil不报错但彻底失效 - 注册后必须配齐两件事:
otel.SetTracerProvider(tp)和otel.SetTextMapPropagator(propagation.TraceContext{}),缺一不可 - 本地调试时加一句
log.Printf("tp: %v", otel.GetTracerProvider()),确认输出不是<nil></nil>
HTTP 入口必须用 otelhttp.NewHandler,别自己解析 traceparent
手写中间件去读 req.Header.Get("traceparent") 再 tracer.Start(),会导致跨 goroutine 传播失败、net.peer.ip 缺失、状态码不自动记录、span name 固定为 HTTP GET 而非真实路由。
- 正确姿势:
http.ListenAndServe(":8080", otelhttp.NewHandler(myHandler, "my-service")) - Gin 用户必须用
ginotel.Middleware,不是gin.Use(otelhttp.NewHandler(...))—— 否则路由参数丢失,span name 变成GET / -
otelhttp.NewHandler第二个参数(span name)不能为空字符串,Jaeger 等导出器会静默丢弃该 span - handler 内部若
panic或提前return,defer span.End()不执行,span 被丢弃——务必确保span.End()在函数退出前被调用
出向 HTTP、DB、goroutine 的 span 断裂:context 必须显式传递
Go 的 context.Context 不跨 goroutine 自动携带 span,db.Query()、http.Client.Do()、go func() {}() 全部默认从 context.Background() 开始新链路。
- 出向 HTTP:用
&http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)},别直接改http.DefaultClient - DB 查询:必须用
db.QueryContext(ctx, ...),不能用db.Query(...);GORM 要调WithContext(ctx) - 启动 goroutine:写成
go doWork(ctx),不是go doWork(context.Background()) - 异步逻辑如
time.AfterFunc、sync.WaitGroup,同样要传ctx,并用context.WithTimeout控制生命周期
最常被忽略的是采样器没配——sdktrace.NeverSample() 是默认行为,不是“低采样”,是彻底不生成任何 span;还有 exporter 连不上 collector 却不报错,只默默丢 trace。这些都不是 bug,是初始化链条上某个环节没扣紧。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











