必须在main()开头注册tracerprovider并调用otel.settracerprovider,否则所有tracer.start()返回空span,导致日志无trace_id、http中间件不生效且全程静默失效。

不注册 TracerProvider,所有 tracer.Start() 都返回空 span —— 日志没 trace_id、HTTP 中间件不生效、也不报错,这是最常被忽略的静默失效。
TracerProvider 必须在 main() 最开头注册
OpenTelemetry Go SDK 的 TracerProvider 是全局单例资源,延迟注册或多次注册会导致 span 断链、trace_id 不一致、采样错乱。
- 必须在
main()函数第一行就完成:先构造sdktrace.NewTracerProvider(),再调用otel.SetTracerProvider(tp) - 禁止放在
init()里 —— 多包并发初始化可能 panic - 禁止在 handler 或中间件里临时创建 —— 不同 goroutine 拿到不同 provider,链路无法关联
- Resource(含
service.name)必须作为选项传入sdktrace.NewTracerProvider(),写反顺序会导致 service.name 丢失
HTTP 入口必须用 otelhttp.NewHandler 包裹
手写解析 traceparent + tracer.Start() 会漏掉语义属性、跨 goroutine 传播失败、状态码不自动记录 —— 它不是“增强”,而是唯一正确绑定 HTTP 生命周期的方式。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 标准
net/http:用http.Handle("/api", otelhttp.NewHandler(myHandler, "api")),不是http.Handle("/api", myHandler) - Gin:必须用
ginotel.Middleware,直接Use(otelhttp.NewHandler(...))会导致路由名变成GET /而非真实路径 - Echo:对应使用
echootel.Middleware -
otelhttp.NewHandler的第二个参数(span name)不能为空,否则部分 exporter(如 Jaeger)会丢弃该 span
出站请求和数据库调用必须显式传播 context
Go 默认不跨 goroutine 传递 span,http.DefaultClient 和原生 sql.Open() 都不会读取 context 中的 trace 信息 —— 这是 80% 的 span 断裂根源。
- HTTP 出站:用
&http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)},别改http.DefaultClient - 数据库:必须用
otelsql.InjectDriver()替换驱动,并通过sql.OpenDB()创建连接池;手动在Query()前后Start()/End()会漏掉连接池等待、驱动解析等关键耗时 - 新起 goroutine:必须传
ctx,写成go doWork(ctx),而非go doWork()或go doWork(context.Background()) - ORM(如 gorm)需链式调用
.WithContext(ctx),否则上下文断开
本地开发优先用 stdoutexporter,但注意它默认只输出 span.End
过早折腾 OTLP collector 容易卡在网络配置上,而 stdoutexporter 能快速验证链路是否通,但默认行为容易让人误判 “span 没生效”。
- 用
stdouttrace.NewExporter(stdouttrace.WithPrettyPrint())启动,能看到完整的 start/end 时间戳 - OTLP exporter 连不上 collector 时默认静默丢 trace,不报错也不打 log —— 检查点应是网络连通性、endpoint 地址、TLS 配置,而非代码逻辑
- 生产环境禁用
sdktrace.AlwaysSample();推荐sdktrace.TraceIDRatioBased(0.01),避免高 QPS 下 exporter 积压拖垮 CPU - 传播器(
propagation.TraceContext{})必须在otel.SetTracerProvider()后立即设置,否则上游 traceparent 无法被下游服务提取
最容易被忽略的是:所有中间件、异步任务、数据库调用,都依赖同一个 context 流动;一旦出现 context.Background() 或未透传的 channel receive,链路就在那里断了,且没有任何提示。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










