tp.Shutdown必须显式调用,否则短生命周期服务会丢失最后一批span;otelhttp.NewHandler须置于最外层以确保context传递;OTLP endpoint需协议与路径匹配collector实际配置;跨goroutine必须手动传ctx,不可依赖context.WithValue。

tp.Shutdown 必须显式调用,不能靠 defer 或忽略
短生命周期服务(比如 CLI 工具、FaaS 函数)里,不调 tp.Shutdown 就退出,最后一批 span 几乎必然丢失。Go 的默认 exporter 是异步的,有缓冲队列和独立 goroutine,但不会自动阻塞等待 flush 完成。
- 在
main函数末尾直接调tp.Shutdown(context.Background()),别只写defer tp.Shutdown(...)—— 如果中间调了os.Exit,defer 不会执行 - 如果用了
http.Server,先等server.Shutdown()返回,再调tp.Shutdown() - 测试时容易漏:mock exporter 不报错,但真实
otlphttp或jaegerexporter 会静默丢数据,且无日志提示
otelhttp.NewHandler 必须是最外层 HTTP handler
HTTP 中间件不是“插上就生效”,它依赖 req.Context() 携带 span。如果把它放在日志、鉴权、CORS 等中间件之后,下游就拿不到 trace context,导致日志没 trace_id、span 全是孤立 root。
- 确保
otelhttp.NewHandler包裹的是最终业务 handler,而不是某个中间件链的中间产物 - 不要用它包裹已带自定义逻辑的 handler(比如封装了重试或超时的 wrapper),否则 context 传递可能被截断
- 如果你用的是
gin或echo,中间件注册顺序必须把 OTel 放最前;net/http原生则注意http.Handle的包装层级
OTLP endpoint 要写全路径,协议要匹配 collector 实际监听方式
设了 OTEL_EXPORTER_OTLP_ENDPOINT 却连不上,大概率不是地址写错,而是协议或路径没对齐。Go SDK 默认用 gRPC(端口 4317),但很多本地 collector(如 docker run otel/opentelemetry-collector)默认只开 HTTP 接口(4318)。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 用 HTTP exporter 时,endpoint 必须包含完整路径:
http://collector:4318/v1/traces,不能只写http://collector:4318 - 用 gRPC exporter 时,endpoint 写
collector:4317,且 collector 配置里要启用otlp/grpcreceiver - 检查 collector 日志是否打印
Listening for ... on port 4317/4318,确认协议与端口实际开启
跨 goroutine 和非 HTTP 场景必须手动传 ctx
context.WithValue 塞 span 不起作用——OpenTelemetry 依赖标准传播器(如 propagators.TraceContext)编解码,不是靠 key-value 存取。
立即学习“go语言免费学习笔记(深入)”;
- 启动新 goroutine 时,必须显式传入带 span 的 ctx:
go doWork(ctx),不能闭包捕获外层变量 - 数据库调用要用
db.QueryContext(ctx, ...),且 driver 需用otel.WrapDriver注册(如pgx或sql包) - 消息队列消费时,从消息 header 提取:
propagators.TraceContext{}.Extract(ctx, carrier),再用trace.WithRemoteParent启新 span

















