OpenTelemetry Go SDK 是当前唯一可持续维护、云厂商兼容、语义标准统一的链路追踪方案,需严格遵循初始化顺序、传播器设置、采样策略及手动埋点规范。

别用 opentracing-go,它已归档;也别裸写 traceparent 头,链路会在第一个 HTTP 跳就断掉。OpenTelemetry Go SDK 是当前唯一可持续维护、云厂商兼容、语义标准统一的方案。
初始化 TracerProvider 时采样器和传播器顺序不能错
TracerProvider 必须在 otel.SetTracerProvider() 之前完成配置,而传播器(如 propagation.TraceContext{})必须在任何 span 创建前通过 otel.SetTextMapPropagator() 设置好。这两步颠倒,下游服务收不到 traceparent,整条链路从入口就断成孤岛。
- 错误做法:先调
otel.SetTracerProvider(tp),再设 propagator —— 此时已有 goroutine 可能已开始创建 span,但 propagation 尚未生效 - 正确顺序:构造
sdktrace.NewTracerProvider(...)→otel.SetTracerProvider(tp)→otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(...)) - 生产环境禁用
sdktrace.AlwaysSample():QPS 过千时 OTLP exporter 会积压、重试风暴、拖垮 CPU 和网络 - 推荐采样器:
sdktrace.TraceIDRatioBased(0.01)(按 trace ID 哈希后取 1%,不是“每 100 请求采 1 个”,低频服务可能长期无数据)
Gin 入口中间件必须手动 Extract 并注入 context
c.Request.Context() 是干净的,不带任何 span 信息。如果你没在中间件里用 propagator.Extract() 恢复 parent span,trace.SpanFromContext(c.Request.Context()) 一定为 nil,后续所有自动埋点都失效。
- 必须在
otelgin.Middleware之前或内部显式调用:ctx := propagator.Extract(r.Context(), r.Header) - 然后把新 ctx 注入 handler:例如
c.Request = c.Request.WithContext(ctx),否则 Gin 的上下文链就断了 - 别手动读
r.Header.Get("traceparent")—— OpenTelemetry 的 propagator 会处理大小写、多值、W3C 格式校验等细节 - 如果用了
otelgin.Middleware,它已帮你做了 Extract + 自动创建 root span,此时你无需再手动 Start,否则会嵌套出无效 span
HTTP 出站请求要用 otelhttp.Transport 或手动 Inject
otelhttp.Transport 只负责 outbound,且前提是你的 context 里已有有效 span(即上游已传入或本服务已创建)。它不会重复 Inject,但如果你自己又调一次 propagator.Inject(),会导致 header 写两次,触发 W3C 校验失败。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 推荐方式:直接用
&http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} - 手动方式(需细粒度控制):在
http.NewRequestWithContext()前,调用propagator.Inject(ctx, httpHeaderCarrier),其中httpHeaderCarrier是实现了textmap.TextMapCarrier的结构体(如http.Header) - 注意:
otelhttp.Transport默认把整个 roundtrip 包进一个 span,不区分 DNS/TLS/write/read;如需拆解,得自己封装 transport 或用otelhttp.WithSpanOptions()加子 span - 它默认不记录
http.status_text或自定义 header,要加就得显式传otelhttp.WithSpanOptions(trace.WithAttributes(...))
数据库/消息队列/本地方法必须手动埋点
HTTP/gRPC 框架能自动抓 request ID 和 span context,但 database/sql、redis.Client、kafka.Consumer、甚至耗时长的本地函数,OpenTelemetry Go SDK 不会自动包裹——必须手动调 tracer.Start() 或 span.AddEvent()。
- Root Span 场景(如 goroutine 启动任务):用
trace.WithNewRoot(),否则 span 会挂错 parent - 手动 Start 示例:
ctx, span := tracer.Start(ctx, "db.query", trace.WithSpanKind(trace.SpanKindClient)) - 加属性:
span.SetAttributes(attribute.String("db.statement", query)) - 记录错误:
span.RecordError(err); span.SetStatus(codes.Error, err.Error()) - 别用
context.WithValue()传 span:它只在单进程内有效,跨 HTTP/gRPC 调用后下游完全不可见
最容易被忽略的是传播器初始化时机和手动埋点的 span kind 设置——前者导致链路断裂,后者导致服务拓扑图里出现大量 “unknown” 节点。事情说清了就结束。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










