http入口必须用otelhttp.newhandler,因其自动处理traceparent解析、span生命周期、状态码/peer.ip等语义属性及跨goroutine context传播;手动解析+startspan会漏关键字段且导致链路断裂。

为什么 HTTP 入口必须用 otelhttp.NewHandler,不能自己解析 traceparent
因为手动解析 header + tracer.Start() 会漏掉 OpenTelemetry 对 HTTP server 的语义约定:它不会自动记录 http.status_code、net.peer.ip、http.route,也不会在 WriteHeader 后精准结束 span;更关键的是,它不处理跨 goroutine 的 context 传播——比如你在 handler 里启了个 go func() { ... },里面的 span 就会变成 root,链路直接断裂。
正确做法是把最终业务 handler 包进 otelhttp.NewHandler,且确保它处于中间件链最外层(别嵌套在其他自定义中间件之后)。Gin/Echo 用户请直接用 ginotel.Middleware 或 echootel.Middleware,否则 span name 会退化成 GET / 而非真实路由。
数据库调用怎么埋点才不丢连接池和驱动耗时
手动在 db.Query() 前后调 tracer.Start()/span.End() 是错的:它只测了 SQL 执行时间,完全忽略连接获取、参数绑定、驱动解析、网络往返等真实瓶颈。OpenTelemetry 官方插件 go.opentelemetry.io/contrib/instrumentation/database/sql 才能 hook 到 driver.Conn 底层。
- 必须用
sql.OpenDB(otelsql.InjectDriver("mysql", driver))替代sql.Open() - MySQL 驱动限定为
github.com/go-sql-driver/mysql,modernc.org/sqlite等纯 Go 驱动不兼容 hook 点 - 敏感字段(如 SQL 中的 user_id)默认不脱敏,生产环境需配
otelsql.WithSpanNameFormatter固定 span 名
TracerProvider 不全局复用会导致什么
后果是静默失效:所有 otel.Tracer().Start() 返回的都是 nil span,日志没 trace_id,HTTP handler 不生成 server span,但程序完全不报错——这是最常被忽略的“黑盒”问题。
必须在 main() 函数最开头注册,且只做一次:
- 别放
init():单元测试并发初始化会 panic - 别在 handler 里 new:导致多个 provider 冲突、采样逻辑错乱、exporter 连接泄漏
- 注册后必须同时调
otel.SetTracerProvider(tp)和otel.SetTextMapPropagator(propagation.TraceContext{}),缺一不可
goroutine 和第三方 SDK 怎么传 context 才不断链
Go 的 context 不会自动跨 goroutine 传递 span。典型翻车场景:HTTP handler 里写 go doWork(),结果 doWork 里的 span 是 root,trace_id 全 0。
解决方法很直白:
- 显式传参:
go doWork(ctx),而不是go doWork(context.Background()) - HTTP 出向请求必须用
otelhttp.NewTransport包装 transport,原始http.DefaultClient会断链 - 第三方 SDK(如 AWS SDK Go v2)要确认是否支持传入自定义
*http.Client;不支持就换otelaws插件 - GORM 等 ORM 若未适配 OTel,得手动用
otel.GetTextMapPropagator().Inject()补traceparentheader
真正难的不是埋点本身,而是每个 context 传递节点都得人工校验:上游塞了没,下游取了没,中间有没有被覆盖或丢弃。稍有遗漏,整条链就碎成几段。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











