生产环境首选 OpenTelemetry Go SDK(go.opentelemetry.io/otel),它已取代 OpenTracing 和 Jaeger 原生客户端,成为 CNCF 毕业项目和事实标准;需正确配置传播器、手动透传上下文、显式管理 Span 生命周期,并用 stdout exporter 本地验证链路完整性。

Go 微服务中该用哪个分布式追踪库
生产环境首选 OpenTelemetry Go SDK(go.opentelemetry.io/otel),它已取代 OpenTracing 和 Jaeger 原生客户端,成为 CNCF 毕业项目和事实标准。旧方案如 opentracing-go 或直接集成 jaeger-client-go 会遇到 Span 生命周期管理混乱、上下文透传不一致、采样策略难统一等问题。
关键判断:不要自己封装 tracer,直接用官方 SDK + 标准 exporter。
-
otel.Tracer("service-name")获取 tracer,名称建议与服务名一致,便于后端识别 - 必须调用
otel.SetTextMapPropagator设置传播器(如B3或W3C),否则 HTTP 请求头无法注入/提取 trace ID - 避免在
init()中初始化全局 tracer——应延迟到配置加载完成后,否则日志、指标等依赖可能未就绪
HTTP 请求如何自动透传 trace context
Go 的 net/http 不自动处理 tracing 上下文,必须手动注入和提取。常见错误是只做注入(client 端)或只做提取(server 端),导致链路断裂。
正确做法是:在 client 发起请求前,用 propagator.Inject 写入 header;在 server 接收请求后,用 propagator.Extract 从 header 恢复 context。
立即学习“go语言免费学习笔记(深入)”;
func injectTraceToRequest(ctx context.Context, req *http.Request) {
prop := otel.GetTextMapPropagator()
prop.Inject(ctx, propagation.HeaderCarrier(req.Header))
}
func extractTraceFromRequest(req *http.Request) context.Context {
prop := otel.GetTextMapPropagator()
return prop.Extract(context.Background(), propagation.HeaderCarrier(req.Header))
}
- 务必使用
propagation.HeaderCarrier而非自定义 map,否则 B3 头(如X-B3-TraceId)或 W3C 头(traceparent)格式不兼容 - 若用 Gin/Echo 等框架,需在中间件中调用
extractTraceFromRequest并将 context 传入 handler,不能仅靠req.Context()—— 它默认不含 trace span - gRPC 场景换用
otelgrpc.Interceptor,别手写 metadata 透传
Span 生命周期管理最容易出错的三个点
Span 泄漏、父子关系错乱、结束过早,是 Go 分布式追踪中最常导致“断链”或“span 堆积”的原因。
- Span 必须显式调用
span.End(),且应在 defer 中执行:defer span.End()。忘记 defer 或提前 return 会导致 span 永远不结束 - 子 Span 必须基于父 context 创建:
tracer.Start(ctx, "db.query"),这里的ctx必须是上层传入、含 parent span 的 context,不能用context.Background() - 异步操作(如 goroutine、callback)需用
trace.ContextWithSpan显式传递 span,Go 的 goroutine 不继承 parent context,直接用原 ctx 启动会丢失 trace 关系
本地开发时如何快速验证 trace 是否连通
不用等部署到 Jaeger UI,本地可直接用 otlphttp exporter 推送到 otel-collector,或更轻量地启用 stdout exporter 查看原始 span 数据。
import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" exp, _ := stdouttrace.New(stdouttrace.WithPrettyPrint()) tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp), ) otel.SetTracerProvider(tp)
启动后发起一次跨服务调用,观察终端输出是否出现嵌套的 name 字段(如 "http.request" → "db.query")、相同 TraceID、且 ParentSpanID 与上层 SpanID 匹配。不匹配说明上下文没传下去,或 span 创建时用了错误的 ctx。
真正棘手的是跨 goroutine + context.Value 混用的场景——trace context 被覆盖或丢弃,这种问题不会报错,但链路静默断裂,只能靠 stdout 输出逐层比对 TraceID。










