不能只用 context.withvalue 传 traceid,因它仅限单进程内有效且无法跨进程传播,必须通过 w3c traceparent 头或 grpc metadata 显式注入与解析,并确保 otel propagator 统一使用及 context 正确透传至所有 i/o 点。

直接用 context.WithValue 存 traceID 字符串,链路必然断裂——因为下游服务拿不到 parentSpanID、采样标志、baggage 等关键字段,无法生成合法子 span。
为什么 context.Context 不能只靠 WithValue 传追踪信息
Go 的 context.Context 本身不带跨进程传播能力,WithValue 只在单进程内有效。HTTP/gRPC 调用时,下游服务根本收不到你塞进 ctx 的值,除非你手动把它编码进请求头或 metadata。
- 常见错误:在 HTTP handler 里用
context.WithValue(req.Context(), traceKey{}, id),然后以为后续所有 DB 查询、gRPC 调用都会自动带上——其实不会,它们只认自己收到的原始req.Context()或显式传入的ctx - 更隐蔽的问题:即使你在客户端手动把
traceID写进req.Header.Set("X-Trace-ID", id),下游服务若没解析并注入到自己的context中,链路还是断的 - OpenTelemetry SDK 已通过
context.Context隐式管理 span 生命周期,重复用WithValue存traceID反而干扰自动提取逻辑
HTTP 请求中必须用 W3C traceparent 头,不是自定义头
W3C TraceContext 是跨语言、跨框架的事实标准,traceparent 头包含 trace-id、span-id、采样标志(01/00)和 trace-flags,下游 SDK 才能正确重建上下文。
- 别写
X-Trace-ID: abc123—— OpenTelemetry Go 默认不识别它;要写traceparent: 00-abc123-def456-01 - 用
otel.GetTextMapPropagator().Extract(ctx, propagation.HeaderCarrier(r.Header))解析,不是r.Header.Get("traceparent")后自己拼 - 客户端发请求时,必须用
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))注入,不能手动生成字符串 - 若对接 Zipkin/Jaeger 老系统,需显式注册复合 propagator:
propagation.NewCompositeTextMapPropagator(propagation.B3{}, propagation.TraceContext{})
gRPC 场景下必须用 metadata.MD + 拦截器,不能只改 context
gRPC 不会自动序列化 context.Value,必须靠 metadata.MD 封装,并在客户端调用和服务端拦截器中显式处理。
- 客户端:构建
md := metadata.Pairs("traceparent", "00-..."),再传grpc.Metadata(md)作为CallOption - 服务端:在 unary interceptor 中调用
md, _ := metadata.FromIncomingContext(ctx),再用 propagator 提取:otel.GetTextMapPropagator().Extract(ctx, MD{md}) - 注意 key 大小写:
metadata.Pairs("TraceParent", ...)会被转成小写traceparent,前后端命名必须统一 - 别漏掉
ctx本身:调用client.SomeMethod(ctx, req),否则下游无法继承超时和取消信号
otel.Tracer 不能全局声明,必须按需获取
写 var tracer = otel.Tracer("my-service") 看似省事,但极易返回 NoopTracer——因为 SDK 可能还没初始化,或者不同模块用了不同 name 导致 tracer 隔离失效。
- 每次需要 tracer 时,都应调用
otel.Tracer("my-service"),由 SDK 内部做缓存和初始化判断 - 确保
otel.SetTracerProvider(tp)在 main 初始化早期执行,早于任何Tracer调用 - 微服务多模块场景下,各包不应维护自己的 tracer 变量,避免因初始化顺序错乱导致追踪静默失效
真正容易被忽略的是:链路是否连通,不取决于你有没有传 traceID,而取决于上下游是否使用同一套 propagator 解析/注入,且是否把 context 正确透传到每个 I/O 操作点——DB 查询、HTTP client、gRPC client、消息队列 producer,缺一不可。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











