必须在main()开头注册sdktrace.TracerProvider并调用otel.SetTracerProvider(),否则所有otel.Tracer().Start()返回空span,导致Jaeger无trace_id、日志无报错、HTTP中间件无数据——这是最典型的静默失效。

必须在 main() 开头注册 sdktrace.TracerProvider 并调用 otel.SetTracerProvider(),否则所有 otel.Tracer().Start() 返回的都是空 span——Jaeger 里看不到 trace_id,日志无报错,HTTP 中间件看似运行却无数据,这是最典型的静默失效。
TracerProvider 初始化顺序和时机不能错
全局唯一、早于任何 handler 启动、不能放在 init() 或某个 handler 内部:
-
sdktrace.NewTracerProvider()必须传入sdktrace.WithResource(res),且res(含service.name)要在 provider 创建前就准备好,写反了 service 标签全丢 - 采样器必须显式配置:
sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.01)),默认是sdktrace.NeverSample(),不是低采样,是彻底不生成 span - 传播器必须设:
otel.SetTextMapPropagator(propagation.TraceContext{}),缺了它,traceparent头不会被自动注入或提取 - 别用
sdktrace.AlwaysSample()上生产——QPS 高时 exporter 积压、重试风暴、拖垮网络
HTTP 入口和出口必须用 otelhttp 封装
otelhttp.NewHandler 只负责入向解析,otelhttp.Transport 或 otelhttp.NewClient 才管出向透传,二者缺一不可:
- 入向:对
net/http,直接http.ListenAndServe(":8080", otelhttp.NewHandler(mux, "my-service"));对 Gin,必须用gin.Use(otelgin.Middleware()),不能gin.WrapH(otelhttp.NewHandler()),否则路由名退化为GET / - 出向:不能继续用
http.DefaultClient,要改用otelhttp.NewClient()或http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)},否则下游收不到traceparent -
otelhttp.NewHandler的第二个参数(span name)不能为空字符串,Jaeger 会直接丢弃该 span - handler 内 panic 或提前 return,会导致
defer span.End()不执行,span 被静默丢弃
数据库、gRPC、goroutine 等非 HTTP 场景必须显式传 context
Go 的 context.Context 不跨 goroutine 自动传递,DB 驱动也不读 context 里的 span,不手动传就断链:
立即学习“go语言免费学习笔记(深入)”;
- 起新 goroutine 时,必须
go doWork(ctx),不能go doWork();time.AfterFunc、sync.WaitGroup同理 - DB 查询必须用
db.QueryContext(ctx, ...),不能用db.Query();GORM 要链式调用.WithContext(ctx) - gRPC 客户端调用必须传
ctx,且用go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc插件,原生grpc.ClientConn不埋点 - 消息队列消费时,需从消息 header 提取
propagation.Extract(),再用trace.WithRemoteParent()构建 child span
最容易被忽略的是:K8s 环境下 OTEL_EXPORTER_OTLP_ENDPOINT 必须写全协议 + host + port + path(如 http://otel-collector.default.svc.cluster.local:4318/v1/traces),写成 localhost 或漏掉 /v1/traces 会静默失败,DNS 解析失败也不报错,只丢 span。


















