必须在main()最早期注册tracerprovider,否则所有span为nil且静默失效;需调用otel.settracerprovider和otel.settextmappropagator,http用otelhttp.newhandler,数据库用otelsql.injectdriver,资源属性必设service.name。

必须在 main() 最早期注册全局 TracerProvider,否则所有 Tracer.Start() 都返回空 span——不报错、无日志、链路彻底静默消失,这是 90% 的初学者卡点。
TracerProvider 初始化必须在 main() 开头且仅一次
OpenTelemetry Go SDK 不会自动 fallback 到默认实现;未调用 otel.SetTracerProvider() 时,otel.Tracer() 返回的是 noop tracer,所有 Start() 调用都静默失败。
- 别在
init()函数里注册:Go 的init执行顺序不可控,HTTP server 可能比 tracer provider 先启动 - 别在 handler 或中间件里临时 new:会导致多个 provider 竞争、exporter 连接泄漏、采样逻辑混乱
- 必须显式传入
resource.Resource,至少带service.name属性,否则 Jaeger / Tempo 无法按服务分组 - 本地调试建议用
stdoutexporter.NewUnstarted()+.Start(),它会输出完整 span JSON,比查日志快得多
HTTP 框架必须用对应 OTel 中间件,不能手写 header 解析
手动调 req.Header.Get("traceparent") + tracer.Start() 看似可行,实则漏掉三类关键信息:跨 goroutine 的 context 传播、net.peer.ip 等语义属性、自动状态码记录(4xx/5xx 不会打 tag)。
- 原生
http.ServeMux:用otelhttp.NewHandler()包裹最终 handler,不是中间件链里的某一层 - Gin:必须用
ginotel.Middleware(),否则 span name 是GET /而非GET /users/:id - Echo:必须用
echootel.Middleware(),同理解决路由名问题 - 所有中间件里创建 child span,必须从
r.Context()取 context,严禁用context.Background()
数据库调用必须走 instrumented driver,不能手动埋点
在 db.Query() 前后自己 tracer.Start()/End(),测出来耗时远小于实际 SQL 执行时间——因为没捕获连接池等待、驱动解析、网络往返等环节。
- MySQL 必须用
github.com/go-sql-driver/mysql,不能用modernc.org/sqlite等纯 Go 驱动变体(hook 点不兼容) - 初始化 DB 时必须用
sql.OpenDB(otel.WrapDriver(driver, &otel.Config{})),而非sql.Open() - span 名默认是
SELECT FROM users,生产环境务必配WithSpanNameFormatter改成固定名,避免高基数打爆 backend - GORM 用户注意:GORM v1.25+ 内置 OTel 支持,但需显式启用
gorm.WithOpentelemetry(),且底层仍依赖上述 SQL driver 注册
goroutine 和异步调用必须显式传 context,不能靠闭包捕获
HTTP handler 里起 go func() { ... }(),里面调 tracer.Start() 生成的一定是 root span,不是 child span——本质是 Go 的 context 不自动跨 goroutine 传递。
- 正确写法:
go doWork(ctx),并在doWork内部用tracer.Start(ctx, ...) - HTTP 客户端发请求前,必须
req = req.WithContext(ctx),否则otelhttp.Transport拿不到 parent span - 所有
Span.End()必须执行,漏掉会导致 span 卡在 batch exporter 缓冲区,几小时后内存缓慢上涨至 OOM - 第三方库(如老版本 ent/gorm)若未适配 OTel,需手动用
otel.GetTextMapPropagator().Inject()补traceparentheader
最易被忽略的其实是 context 传递断裂:上游有 trace_id,下游 trace_id 全 0 或 parent_span_id 为空,99% 是因为你忘了把带 span 的 context 显式传进 goroutine、database/sql 调用、或 HTTP client 请求里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











