Go服务接入OpenTelemetry核心是确保context透传:用otel.Tracer获取tracer、显式传递context、HTTP/gRPC调用需Inject/Extract、使用otelhttp/otelgrpc拦截器、正确配置exporter并调用shutdown。
Go 服务要实现链路追踪,首选是 OpenTelemetry(OTel),它已成云原生事实标准。直接用 opentelemetry-go SDK + 对应 exporter 即可,无需再绕道 Jaeger 或 Zipkin 客户端。
关键不是“能不能接”,而是“是否漏掉了 context 透传”——90% 的断链问题都出在这里。
otel.Tracer("your-service-name") 获取 tracer,不能自己 newcontext.Context,且需用 otel.GetTextMapPropagator().Inject() 注入 trace headersotel.GetTextMapPropagator().Extract() 解析 incoming headers,否则子 span 无法关联父 spanotelhttp.NewHandler() 包裹 HTTP handler,它自动完成 extract/inject 和 span 生命周期管理手动注入 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 接口。
resty 或 go-resty/resty/v2,需注册自定义 BeforeRequest 回调来 injectnet/http 原生 client,建议直接换为 otelhttp.NewClient(),它自动 wrap transport 并处理透传req.Header.Set("traceparent", ...) —— tracestate、sampling decision 等字段会被忽略,导致采样不一致或丢失 baggagegRPC 的 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.StreamI
nterceptor(otelgrpc.StreamServerInterceptor()),
)
otelgrpc.UnaryServerInterceptor 会自动从 metadata.MD 中 extract trace context,无需手动解析UnaryServerInterceptor 链,确保 otelgrpc 拦截器在最外层(最先执行),否则 extract 失败grpc.WithBlock() 或正确处理连接状态,异步 dial 可能导致 span parent 为空本地跑通但看不到链路?大概率是 exporter 配置或网络问题,而不是代码逻辑错误。
otlphttp,endpoint 通常设为 http://localhost:4318/v1/traces,确认你的 OTel Collector 正在监听该地址和路径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,整条链就断了,而且很难定位。这种断点往往藏在日志中间件、重试逻辑、或者异步任务启动处。