Go服务接入OpenTelemetry核心是确保context透传:用otel.Tracer获取tracer、显式传递context、HTTP/gRPC调用需Inject/Extract、使用otelhttp/otelgrpc拦截器、正确配置exporter并调用shutdown。

Go 服务接入 OpenTelemetry 的核心步骤
Go 服务要实现链路追踪,首选是 OpenTelemetry(OTel),它已成云原生事实标准。直接用 opentelemetry-go SDK + 对应 exporter 即可,无需再绕道 Jaeger 或 Zipkin 客户端。
关键不是“能不能接”,而是“是否漏掉了 context 透传”——90% 的断链问题都出在这里。
- 必须用
otel.Tracer("your-service-name")获取 tracer,不能自己 new - 所有跨 goroutine、HTTP、gRPC、数据库调用,都要显式传递
context.Context,且需用otel.GetTextMapPropagator().Inject()注入 trace headers - HTTP server 端必须用
otel.GetTextMapPropagator().Extract()解析 incoming headers,否则子 span 无法关联父 span - 推荐使用
otelhttp.NewHandler()包裹 HTTP handler,它自动完成 extract/inject 和 span 生命周期管理
HTTP client 端 trace 透传的常见写法
手动注入 trace context 到 HTTP 请求 header 是最易出错环节。不用 otelhttp.Transport 时,必须自己处理。
req, _ := http.NewRequest("GET", "http://backend:8080/api", nil)
ctx := context.Background()
// 必须从当前 span 获取 context,而非空 context
span := trace.SpanFromContext(ctx)
ctx = trace.ContextWithSpan(ctx, span)
// 注入 traceparent、tracestate 等 header
otel.GetTextMapPropagator().Inject(ctx, otelhttp.HeaderCarrier(req.Header))
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
注意:req.WithContext(ctx) 不可省略,否则 goroutine 内部 span 关联失败;otelhttp.HeaderCarrier 是适配器,把 http.Header 转成 OTel 要求的 carrier 接口。
立即学习“go语言免费学习笔记(深入)”;
- 若用
resty或go-resty/resty/v2,需注册自定义BeforeRequest回调来 inject - 若用
net/http原生 client,建议直接换为otelhttp.NewClient(),它自动 wrap transport 并处理透传 - 不要手写
req.Header.Set("traceparent", ...)—— tracestate、sampling decision 等字段会被忽略,导致采样不一致或丢失 baggage
gRPC Go 客户端和服务端 trace 配置要点
gRPC 的 trace 依赖 otelgrpc 拦截器,但默认不启用。不配置拦截器,整个 gRPC 调用就只有一层 span,上下游无法串联。
客户端示例:
conn, _ := grpc.Dial("backend:9000",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
)
服务端示例:
s := grpc.NewServer(
grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()),
)
-
otelgrpc.UnaryServerInterceptor会自动从metadata.MD中 extract trace context,无需手动解析 - 若服务端用了自定义
UnaryServerInterceptor链,确保otelgrpc拦截器在最外层(最先执行),否则 extract 失败 - gRPC metadata 透传依赖
grpc.WithBlock()或正确处理连接状态,异步 dial 可能导致 span parent 为空
本地开发时 trace 数据发不到 collector 的典型原因
本地跑通但看不到链路?大概率是 exporter 配置或网络问题,而不是代码逻辑错误。
- 默认 exporter 是
otlphttp,endpoint 通常设为http://localhost:4318/v1/traces,确认你的 OTel Collector 正在监听该地址和路径 - 如果 Collector 启在 Docker,Go 进程用
localhost访问不到,得换成host.docker.internal(Mac/Win)或宿主机真实 IP(Linux) - 忘记调用
shutdown()—— OTel SDK 默认 batch 上报,进程退出前未 flush,最后几个 span 就丢了 - 采样器设成了
otel.AlwaysSample()才能 100% 看到所有请求;生产环境常用otel.ParentBased(otel.TraceIDRatioBased(0.1)),低流量下可能一个 trace 都看不到
真正难的从来不是“怎么加 tracer”,而是让每个中间件、每层封装、每个第三方库调用都持续携带 context。一旦某处用了 context.Background() 或忘了 inject/extract,整条链就断了,而且很难定位。这种断点往往藏在日志中间件、重试逻辑、或者异步任务启动处。










