TracerProvider 必须在 main() 开头注册,否则 span 为 nil;需调用 otel.SetTracerProvider 和 otel.SetTextMapPropagator;HTTP 入口用 otelhttp.NewHandler,出向用 otelhttp.Transport;数据库用 otelsql.InjectDriver;避免高基数字段作 attribute。

TracerProvider 必须在 main() 开头就注册,否则所有 span 都是空的
Go 里 otel.Tracer() 调用不会报错,但如果你没提前注册 sdktrace.TracerProvider,它返回的 span 永远是 nil,日志里看不到 trace_id,HTTP 中间件也不生效——这是最典型的“静默失效”。
- 必须在
main()函数最开始、任何 handler 启动前完成注册,全局只做一次 - 别放在
init()里:单元测试并发初始化会 panic - 别在某个 HTTP handler 里临时 new 一个:会导致多个 provider 冲突,采样逻辑错乱
- 注册后务必调用
otel.SetTracerProvider(tp)和otel.SetTextMapPropagator(propagation.TraceContext{}),缺一不可
HTTP 入口必须用 otelhttp.NewHandler,手写 extract + StartSpan 是错的
很多人自己解析 req.Header.Get("traceparent") 再调 tracer.Start(),结果 span 名字全是 HTTP GET、没有状态码、不带 net.peer.ip、跨 goroutine 传播失败——因为漏掉了 OpenTelemetry 对 HTTP server 语义的完整封装。
-
otelhttp.NewHandler会自动:提取 header、注入 context、记录 method/path 为 span name、捕获 response status、结束 span 时机精准到 writeHeader 完成 - Gin 用户不要用
gin.Use(...)包裸http.Handler;要用ginotel.Middleware,否则路由名丢失(如GET /api/order/:id变成GET /) - 自定义中间件里要起 child span,必须从
r.Context()取 context,不能用context.Background()
下游 HTTP 调用必须用 otelhttp.Transport,http.DefaultClient 是断链元凶
入向用了 otelhttp.NewHandler,但出向还用原始 http.Client,那整个链路只有一跳——下游服务收不到 traceparent,span 就断在第一个服务出口。
- 正确做法:
client := &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} - 千万别直接改
http.DefaultClient:会影响其他未 instrument 的库,且无法隔离采样策略 - 如果用了第三方 SDK(比如 AWS SDK Go v2),要确认它是否支持传入自定义
http.Client;不支持的话得换用otelaws等专用插件
数据库追踪不能靠手动 Start/End,必须用 otelsql.InjectDriver
看到有人在 db.Query() 前后手动埋点,结果 span duration 比实际慢几倍或快几倍——因为没覆盖连接池等待、驱动参数解析、预编译 Stmt 创建、Rows.Scan 等关键阶段。
立即学习“go语言免费学习笔记(深入)”;
-
otelsql.Open()只 wrap 了*sql.DB层,对*sql.Tx或Stmt.Exec无效 - 真正生效的是
otelsql.InjectDriver("postgres", &pq.Driver{})这类底层 driver 注入,它拦截所有database/sql底层调用 - 避免高基数字段当 span attribute:比如把
user_email或request_body直接塞进 span,会撑爆 collector 内存


















