直接用 OpenTelemetry Go SDK 就够了,因其已提供生产就绪的 tracing 实现,覆盖 span 管理、上下文传播、采样和导出等全部核心能力,避免重复造轮子踩坑。

为什么直接用 OpenTelemetry Go SDK 就够了
Go 生态里不需要从零造轮子实现分布式追踪系统。OpenTelemetry Go SDK 已经提供生产就绪的 tracing 实现,覆盖 span 生命周期管理、上下文传播、采样、导出等全部核心能力。自己实现 trace ID 生成、context 注入、跨 goroutine 传递、HTTP header 注入/提取这些逻辑,不仅容易出错,还会重复踩 context 跨协程丢失、span 未结束导致内存泄漏、W3C TraceContext 解析不兼容等坑。
如何在 HTTP handler 中自动注入和提取 trace context
最常见错误是手动拼接 traceparent header 或忽略 carrier 类型差异,导致下游服务收不到 span 上下文。正确做法是使用 otelhttp.NewHandler 包裹 handler,它会自动处理 W3C TraceContext 的提取与注入。
- 不要手动读写
r.Header.Get("traceparent"),改用propagators.Extract(r.Context(), r.Header) - 若需自定义 carrier(比如从 gRPC metadata 或自定义 header 传),必须实现
TextMapCarrier接口,且 key 必须小写(traceparent不是TraceParent) - HTTP client 端要用
otelhttp.NewClient,否则 outbound 请求不会携带 context
goroutine 中 span 丢失的典型原因和修复方式
Go 的 context.Context 不会自动跨 goroutine 传递,这是 Go 分布式追踪中最常被忽略的一点。启动新 goroutine 时,若没显式把带 span 的 context 传进去,新 goroutine 创建的 span 就会变成 root span,链路断裂。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 错误写法:
go doWork()——doWork拿到的是context.Background() - 正确写法:
go doWork(ctx),并在doWork内部用trace.SpanFromContext(ctx)获取 parent span - 对
time.AfterFunc、sync.WaitGroup启动的异步逻辑,同样要传入 context 并用context.WithTimeout控制生命周期
导出器选型:Jaeger vs OTLP vs Zipkin 的实际差异
本地开发用 Jaeger agent 最方便,但生产环境强烈建议直连 OTLP endpoint(如 Tempo、Lightstep、New Relic)。Zipkin 兼容性看似好,但其 span 时间精度为毫秒级,而 OpenTelemetry 默认纳秒级,转换时会丢精度;Jaeger 的 thrift 协议已弃用,新版只支持 OTLP。
立即学习“go语言免费学习笔记(深入)”;
- 开发阶段:用
jaeger.NewExporter+jaegeragent容器,header 名是uber-trace-id(注意不是 W3C 标准) - 生产阶段:用
otlphttp.NewExporter,endpoint 设为https://your-otlp-endpoint/v1/traces,必须配置 TLS 和认证 token - 避免混用:同一服务不要同时启用 Jaeger 和 OTLP 导出器,会造成 span 重复上报和 context 冲突
真正难的不是埋点,而是确保每个异步分支、每个中间件、每个第三方 client 都参与 context 传递。漏掉一个 ctx 参数,整条链路就断在那一点,而且很难定位。

















