Go微服务全链路追踪核心是OpenTelemetry SDK+context.Context透传,需在HTTP入口注入trace context、用otelhttp.NewHandler自动解析、RoundTripper注入headers、异步任务手动传播,漏任一环即断链。

Go 微服务中做请求全生命周期追踪,核心不是“要不要加”,而是“在哪加、怎么传、谁负责采样”。OpenTelemetry SDK + http.RoundTripper + context.Context 透传是当前最稳的组合,但漏掉中间件或异步 goroutine 就会断链。
HTTP 入口处必须注入 trace context
所有 HTTP handler 都要从 request.Context() 提取 traceID 并绑定到 span,否则上游调用链一进来就丢。别依赖框架自动注入——Gin/echo 默认不解析 traceparent header,得手动做。
常见错误:直接用 context.Background() 创建新 span,导致与上游完全无关;或者只在 handler 开头提取 context,但没把新 span 注入回 request context,下游中间件拿不到。
- 用
otelhttp.NewHandler()包裹 handler,它会自动解析traceparent和tracestate - 若自定义中间件(如 auth、rate limit),必须用
request = request.WithContext(span.Context())把 span context 写回去 - 不要在 handler 里调
otel.Tracer.Start(),除非你明确要创建子 span;优先复用request.Context()里的 span
跨服务调用时 RoundTripper 必须注入 trace headers
Go 的 http.Client 默认不传 trace 上下文,otelhttp.NewTransport() 是唯一可靠方案。手写 req.Header.Set("traceparent", ...) 容易格式错、漏采样标志、不兼容 W3C 标准。
立即学习“go语言免费学习笔记(深入)”;
容易踩的坑:用全局 client 复用 transport,但忘了 transport 是有状态的(比如带 otel 的 transport 会读写 context);或者在 goroutine 里发起请求却没把父 span context 传进去,导致子请求无 parent。
- 初始化 client 时必须用
&http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} - 每次
client.Do(req)前,确保req = req.WithContext(parentCtx),其中parentCtx来自当前 span 或 handler context - 避免在
go func() { ... }()里直接调client.Do,应显式传入ctx参数并用req.WithContext(ctx)
异步任务和消息队列消费需手动传播 context
Kafka/RabbitMQ 消费者、定时任务、后台 goroutine 不走 HTTP 协议,traceparent header 不存在,必须靠消息体透传 traceID + spanID,并在消费端重建 span。
典型错误:消费端用 context.Background() 启动 span,导致整条链变成独立根 span;或者只传了 traceID,没传 spanID 和 traceflags,采样决策失效。
- 生产消息前,用
otel.GetTextMapPropagator().Inject(ctx, &yourMessageHeaders)把 context 写进 map[string]string - 消费消息后,先构造空 context,再用
otel.GetTextMapPropagator().Extract()恢复 context,最后用该 context 启动 span - 消息体里存 trace 字段推荐用
map[string]string而非 JSON string,避免序列化破坏 header 格式
采样策略和 exporter 配置直接影响可观测性质量
本地开发全量上报没问题,上生产必须配采样——默认 ParentBased(AlwaysSample) 会让低流量服务也打满 trace 数据,高并发服务则可能压垮 collector。
容易被忽略的是 exporter 超时和重试:默认 5s 超时 + 1 次重试,在网络抖动时大量 span 丢失,且不报错;日志里只看到 “exporter timeout”,没提示是哪个 exporter。
- 生产环境推荐
ParentBased(TraceIDRatioBased(0.01)),即 1% 采样率;对关键路径(如支付)可单独用SpanKindServer+AttributeFilter提升采样率 - 配置
otlphttp.NewExporter()时显式设WithTimeout(10 * time.Second)和WithRetry(otlphttp.RetryConfig{MaxAttempts: 3}) - 检查
otel.Exporter.Otlp.Http.Endpoint是否指向 collector 地址而非 localhost,K8s 环境常因 service DNS 解析失败静默丢数据
真正难的不是埋点,是保证每个 goroutine、每条消息、每次 defer cleanup 都持有正确的 context;一旦某个环节用错 context 或漏传,整条链就断成两截,而这种问题在线上几乎无法复现。


















