context.Context 是链路追踪的基石,因其需显式传递追踪上下文,跨 goroutine、http、gRPC 时若未持续传递则 traceID 丢失,导致 span 断裂。

为什么 context.Context 是链路追踪的基石
Go 的链路追踪不是靠框架自动注入,而是靠 context.Context 显式传递追踪上下文。没有它,span 就断了——哪怕用了 OpenTelemetry 或 Jaeger SDK,跨 goroutine、HTTP、gRPC 时都会丢失 traceID。
关键点在于:你必须在每次调用前把带 trace 信息的 context 传下去,不能只在入口解析一次就丢掉。
- HTTP handler 中用
r.Context()拿原始 context,再用tracer.Start()创建 span,并用ctx, span := tracer.Start(r.Context(), "http_handler")续上链路 - 调用下游服务时,必须把当前
ctx传给 HTTP client 或 gRPC client,比如http.NewRequestWithContext(ctx, ...) - goroutine 启动前,用
ctx = context.WithValue(ctx, key, val)或更安全的context.WithCancel(ctx)派生,避免 context 泄漏
OpenTelemetry Go SDK 初始化常见错误
初始化错一步,整个服务上报的 trace 全是孤立 span,查不到上下游依赖。最常踩的坑是 exporter 配置没生效或 SDK 没注册。
检查这三点:
立即学习“go语言免费学习笔记(深入)”;
- 忘记调用
otel.SetTracerProvider(tp)—— 不设 provider,otel.Tracer("xxx").Start()返回的是空实现,日志里看不到报错,但数据根本不上报 - Jaeger exporter 用
NewExportPipeline时,endpoint写成"localhost:6831"却没开 agent,应改用collector模式:"http://localhost:4317"+otlphttp.NewClient() - 忘记设置全局 propagator:
otel.SetTextMappropagator(oteltrace.B3Propagator{}),导致 HTTP header 中的b3或traceparent字段不被识别或不写入
gRPC 客户端和服务端如何透传 trace 上下文
gRPC 默认不传播 context 中的 span,必须显式配置 interceptor。否则服务 A 调 B,B 的 span parentID 是空的,链路直接断裂。
客户端侧要加 grpc.WithUnaryInterceptor(),服务端侧要配 grpc.UnaryInterceptor(),两者 propagator 必须一致:
- 客户端示例:
conn, _ := grpc.Dial("...", grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor())) - 服务端示例:
srv := grpc.NewServer(grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor())) - 注意
otelgrpc默认用 W3Ctraceparent格式;若老系统用 B3,需传otelgrpc.WithPropagators(b3.New()) - 如果用了自定义 metadata(比如带 auth Token),确保 interceptor 在读取/写入 metadata 前后不覆盖
traceparent字段
本地开发时 trace 数据不显示?先检查这三处
本地跑通但 ui 看不到 trace,90% 是数据根本没发出去,而不是采样率或 UI 配置问题。
- 运行 collector 时没开接收端口:比如
otel-collectorconfig 中receivers:下没启用otlp:或jaeger:,或者端口被防火墙拦截 - Go 程序启动后没等 collector 就退出:加
time.Sleep(5 * time.Second)或用signal.Notify捕获SIGINT,确保 shutdown 时调tp.Shutdown(context.background())刷出缓冲 span - 采样率设成了
TraceIDRatioBased(0.0)或ParentBased(AlwaysOff())—— 本地调试建议直接用AlwaysSample()
链路追踪真正难的不是接入,而是每一层都得对齐 context 生命周期和 propagator 格式。少一个 WithContext(),整条链就断在那一点,还不好定位。