go 的 context.context 不自带 trace id,需通过 context.withvalue 显式注入;必须在入口解析 traceparent 等 header 并用 otel.gettextmappropagator().extract() 提取,失败时 fallback 创建 root span; outbound 请求须手动 inject 到 header,且 traceparent 必须全小写;key 应使用 unexported 类型如 spankey,优先使用 otel.spanfromcontext() 等封装。

Go 的 context.Context 本身不带 Trace ID,得自己塞进去
Go 标准库的 context.Context 是空壳,没有内置 Trace 字段。OpenTracing / OpenTelemetry 都是靠把 trace.Span 或 otel.TraceID 作为 value 塞进 context 实现传播的。这不是“自动继承”,而是靠显式调用 context.WithValue 和配套的取值函数。
常见错误是直接传原始 context,比如:http.HandlerFunc 里没从 request header 提取 trace ID 就往下传,下游 span 全断开。
- 必须在入口(如 HTTP middleware、gRPC interceptor)从
req.Header.Get("traceparent")或自定义 header 解析出 trace 上下文 - 用
otel.GetTextMapPropagator().Extract()(OTel)或opentracing.GlobalTracer().Extract()(OT)做解析,别手写解析逻辑 - 提取失败时,建议 fallback 创建新 root span,而不是传 nil context —— 否则下游
SpanFromContext返回 nil,日志/指标全丢
用 otel.GetTextMapPropagator().Inject() 往 outbound 请求塞上下文
往 HTTP client、gRPC、Redis 等 outbound 调用写 trace header,不是靠 context 自动透传,而是每次发请求前手动调用 Inject。漏掉一次,链路就断在那一跳。
典型场景:你用 http.Client.Do() 调第三方 API,但没在 req.Header 里塞 traceparent,那下游服务根本看不到你的 trace ID。
- 必须在构造 outbound request 后、发送前调用
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header)) -
propagation.HeaderCarrier是 OTel 提供的适配器,把http.Header包装成可写入的 carrier;别自己遍历 header 赋值 - gRPC 场景用
grpc.Dial(..., grpc.WithUnaryInterceptor(...))+otelgrpc.UnaryClientInterceptor()更省心,但得确认版本兼容(v1.20+ 的 otelgrpc 才默认支持 W3C traceparent)
context.WithValue 存 trace 数据,但 key 必须是 unexported 类型
很多人用字符串或 int 当 context key,比如 ctx = context.WithValue(ctx, "span", s),这会导致 key 冲突 —— 其他包也用同样字符串当 key,值就被覆盖了。
Go 官方明确要求 context key 必须是 unexported 类型(首字母小写),确保全局唯一。
- 正确做法:
type spanKey struct{},然后用var spanKeyKey spanKey作 key - OpenTelemetry 已提供
otel.Key类型和otel.SpanFromContext()等封装,优先用它,别重复造轮子 - 别把整个
trace.Span塞 context —— 只存 span 的引用(指针)即可;塞结构体副本或大对象会增加 GC 压力
HTTP header 名大小写敏感,traceparent 必须小写传
Go 的 http.Header 底层用 map[string][]string,key 是规范化的(首字母大写,其余小写),但 W3C trace-context 规范要求 header 名是 traceparent 全小写。如果你用 req.Header.Set("Traceparent", ...),实际发出去的是 Traceparent,下游 OTel SDK 默认不认。
这是最隐蔽的断链原因:本地日志看着有 trace ID,但跨服务后全变成新 trace。
- 必须用
req.Header.Set("traceparent", ...)(全小写)和req.Header.Set("tracestate", ...) - OTel 的
propagation.HeaderCarrier内部已处理大小写,所以用它 Inject/Extract 是安全的;但手写 header 操作时务必注意 - 调试时用
curl -v或 Wireshark 看真实请求 header,别只信代码里写的变量名
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











