Go链路追踪关键在上下文传播可靠、Span生命周期可控、跨服务ID一致;需用OpenTelemetry并显式配置Resource(含service.name)、采样器,手动处理HTTP/gRPC的Context透传与Span注入,规范命名并defer结束Span。

Go 语言链路追踪不是“加个 SDK 就完事”,关键在上下文传播是否可靠、Span 生命周期是否可控、跨服务 ID 是否一致。用 OpenTelemetry 是当前最稳妥的选择,但直接套模板容易踩坑——比如 context 传丢了、span.End() 忘 defer、HTTP header 里没传 X-B3-TraceId。
OpenTelemetry 初始化必须显式配置 Resource 和 Sampler
很多新手只调 otel.SetTracerProvider(tp) 就以为完事,结果本地跑得通,一上生产就收不到数据。根本原因是默认 Resource 为空,部分后端(如 Jaeger、OTLP Collector)会直接丢弃无 service.name 的 trace;同时默认采样器是 ParentBased(TraceIDRatioBased(0.001)),99.9% 的请求被静默丢弃。
- 必须手动注入
service.name:用resource.WithAttributes(semconv.ServiceNameKey.String("order-api")) - 调试阶段用
trace.AlwaysSample(),上线前再切到trace.TraceIDRatioBased(0.01) - 导出器失败时默认静默,建议加
WithErrorFunc(log.Printf)捕获上报异常
HTTP 请求中 Span Context 传播要靠中间件 + Client 透传
服务 A 调用服务 B,如果只在 A 的 handler 里 start span,B 收不到任何 trace 信息——因为 Go 的 http.Request 默认不携带 context 中的 span 数据,必须手动注入 header。
- 服务端用中间件从
X-B3-TraceId/X-B3-SpanId提取并注入context.Context,推荐用otel.GetTextMapPropagator().Extract() - 客户端发请求前,必须调
otel.GetTextMapPropagator().Inject()把当前 span 写进req.Header - 别依赖
req.Context()自动继承:它只继承 parent goroutine 的 context,不自动带 span - Gin 用户注意:
c.Request = c.Request.WithContext(ctx)这步不能省,否则下游中间件拿不到新 context
gRPC 场景下必须用 otelgrpc 拦截器,不能手写
gRPC 的 metadata 传输机制和 HTTP 完全不同,自己 parse metadata.MD 注入 span 极易漏掉 streaming 场景或 cancel 事件。官方 otelgrpc 拦截器已处理所有边界 case。
立即学习“go语言免费学习笔记(深入)”;
- 服务端注册:
grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()) - 客户端注册:
grpc.UnaryInterceptor(otelgrpc.UnaryClientInterceptor()) - 若用了 streaming,还得额外加
grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor())等 - 拦截器内部会自动处理
status.Code、rpc.system、rpc.method等语义约定标签,手写容易错漏
Span 命名和生命周期最容易被忽略的三个点
命名不规范、End 不及时、父子关系断裂,会导致链路图完全不可读——不是数据没上报,而是上报了也连不成树。
-
tracer.Start(ctx, "db.query")比"query"或"handle"更具可检索性;避免动态拼接(如"user-"+id),会爆炸式生成 metric 标签 - 必须
defer span.End(),且确保 span 变量作用域覆盖整个业务块;在 goroutine 里启动 span 时,ctx必须传进去,否则 end 时 context 已失效 - 跨 goroutine 传递 span 要用
otel.ContextWithSpan(ctx, span),而不是直接传*Span指针——后者绕过 context 传播机制,父 span 关闭后子 span 无法正确 link
真正卡住人的从来不是怎么加 tracer,而是 context 在哪一级断开、header 在哪一环没透传、span 在哪个 goroutine 里被提前回收。这些细节不逐行验证,链路图永远是断的。


















