应直接使用 opentelemetry go sdk,因其可规避上下文跨 goroutine 丢失、http/grpc header 传递不一致、采样率配置失效三大高频问题。

直接用 OpenTelemetry Go SDK,别自己手写 Span 传播逻辑——90% 的自研链路追踪模块最终都卡在上下文跨 goroutine 丢失、HTTP/GRPC header 传递不一致、采样率配置失效这三类问题上。
为什么不用 OpenTracing 或自定义 tracer
OpenTracing 已归档,github.com/opentracing/opentracing-go 不再维护;自定义 tracer 表面轻量,实际要覆盖的边界极多:
-
context.WithValue在 goroutine spawn(如go func())后丢失 span,必须用context.WithContext+otel.GetTextMapPropagator().Inject()显式传播 - HTTP 客户端默认不携带
traceparent,需手动 wraphttp.RoundTripper或用otelhttp.NewTransport() - GRPC 调用需注入
otelgrpc.UnaryClientInterceptor和otelgrpc.UnaryServerInterceptor,漏一个就断链 - 采样策略若硬编码为
AlwaysSample,线上压测时日志爆炸;但用ParentBased(TraceIDRatioBased(0.01))又容易漏掉关键错误 trace
初始化 TracerProvider 必须设置 resource 属性
否则所有 span 都会标记为 service.name=unknown,SkyWalking/Jaeger 无法按服务聚合。正确写法:
func initTracer() error {
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.01))),
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
// ⚠️ 关键:resource 必须包含 service.name
r, _ := resource.Merge(
resource.Default(),
resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("order-service"),
semconv.ServiceVersionKey.String("v1.2.0"),
),
)
return tp.Register(r)
}
注意:service.name 值应来自配置项或环境变量,不能写死;ServiceVersionKey 建议绑定 Git commit hash,方便回溯。
HTTP Server 中自动注入 span 的安全写法
别在 handler 里裸调 tracer.Start(),必须从 request context 提取并延续 trace 上下文:
- 使用
otelhttp.NewHandler()包裹你的http.ServeMux,它会自动解析traceparent并注入 context - 若需自定义中间件,确保调用
otelhttp.Extract(ctx, r)获取 span context,再传给后续逻辑 - 避免在 defer 中调用
span.End(),因为 HTTP handler 可能 panic,应改用span.End(otel.WithStackTrace(true))捕获异常栈
示例:
http.Handle("/order", otelhttp.NewHandler(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
span := trace.SpanFromContext(ctx)
span.SetAttributes(attribute.String("user_id", r.URL.Query().Get("uid")))
// 后续业务逻辑...
}),
"order-handler",
))
跨 goroutine 传播 context 的唯一可靠方式
Go 的 context 不会自动穿透 goroutine,以下写法必然丢 span:
go func() {
// ❌ 错误:ctx 未传递,span 丢失
doWork()
}()
正确做法只有两种:
- 显式传参:
go doWork(ctx),并在doWork内部用trace.SpanFromContext(ctx) - 用
otel.GetTracerProvider().ForceFlush()确保 span 上报,但仅用于兜底,不能替代传播
更推荐封装一个带 context 的 worker 工具函数,而不是依赖 goroutine 内部自行取 ctx —— 实际项目中,80% 的 trace 断链都源于此处疏忽。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











