Go服务接入OpenTelemetry需正确初始化TracerProvider并配置OTLP等Exporter,关键在于用otelhttp.NewHandler包裹HTTP handler、下游调用使用otelhttp.NewClient传递context、手动处理消息队列等非HTTP场景的远程父Span提取与注入,同时合理设置ParentBased采样策略,避免孤立Span和上下文丢失。

Go 服务怎么接入 OpenTelemetry 并上报 trace 数据
直接上手:用 opentelemetry-go 官方 SDK,配合一个 exporter(比如 otlphttp 或 jaeger),就能把 trace 发出去。关键不是“能不能接”,而是 trace 是否真正串联、span 是否携带上下文、采样是否合理。
常见错误现象:Span 全是孤立的 root span,HTTP 请求进来的 trace_id 每次都变;或者本地测试能看到数据,一上 K8s 就断连——多半是 context 传递漏了,或 exporter 配置没对齐后端接收地址。
- 必须用
otelhttp.NewHandler包裹 HTTP handler,不能只靠tracer.Start手动创建 span - 下游调用(如 HTTP client)要用
otelhttp.NewClient,否则 context 中的SpanContext不会自动注入到traceparentheader - 非 HTTP 场景(如消息队列消费)需手动从消息元数据中提取
propagation.Extract,再用trace.WithRemoteParent启动新 span - 采样器别用默认的
AlwaysSample上生产——改用ParentBased(TraceIDRatioBased(0.01))更稳妥
为什么 context.WithValue 传 span 会失败
因为 OpenTelemetry 的传播依赖标准 context.Context,但它的 span 是通过 otel.GetTextMapPropagator().Inject() 和 Extract() 编解码的,不是靠 context.WithValue(ctx, key, span) 这种方式传递。自己塞 value 不会被 exporter 识别,也不会透传到下游。
正确做法是始终用 trace.SpanFromContext(ctx) 获取当前 span,并用 trace.ContextWithSpan(ctx, span) 生成新 context——这个 context 才带 propagation 所需的 trace state。
- 所有 goroutine 启动前,必须显式传入带 span 的
ctx,例如go doWork(ctx) - 数据库驱动(如
pgx)要配WithTracer选项,不能指望 ORM 自动埋点 - log 库如果支持
With方法,可传入trace.SpanFromContext(ctx).SpanContext()提取 trace_id/span_id 辅助排查
OTLP HTTP exporter 连不上 collector 怎么查
最常卡在 TLS 和 endpoint 路径。OpenTelemetry Collector 默认监听 http://localhost:4318/v1/traces,但 Go client 的 otlphttp.NewClient 默认发到 /v1/traces,所以 endpoint 必须写全(含路径),不能只写 http://collector:4318。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
错误信息如 rpc error: code = Unavailable desc = connection closed before server preface received,大概率是 HTTP/2 握手失败——要么 collector 没开 HTTP/2,要么 client 被 proxy 拦截了 upgrade 请求。
- 先用
curl -X POST http://localhost:4318/v1/traces -H 'Content-Type: application/x-protobuf' --data-binary @empty.bin测试 endpoint 可达性(empty.bin是空 protobuf 文件) - Go client 开启调试日志:
otel.SetLogger(zap.NewNop())+ 设置环境变量OTEL_GO_LOG_LEVEL=debug - K8s 环境优先用 service name(如
otel-collector.default.svc.cluster.local:4318),避免硬编码 IP - 若 collector 启用了 TLS,client 必须配
otlphttp.WithInsecure()(开发)或otlphttp.WithTLSClientConfig()(生产)
自定义 span 名称和属性要注意什么
span 名称不该是固定字符串(如 "db.query"),而应反映实际操作,比如 "SELECT users WHERE id=?" 或 "payment_service.charge"。属性同理:不要打 user_id: "123" 这种明文,敏感字段得过滤;也不要无脑 dump 整个 request body。
性能影响很实际:每个 SetAttributes 都触发一次 map copy,高频 span(如每秒万级)里加 10+ 属性会明显拖慢。
- 名称控制在 50 字符内,避免动态拼接长路径(如
r.URL.Path带 query 参数) - 属性键统一用
semconv包里的常量(如semconv.HTTPMethodKey),方便后端做标准化聚合 - 错误处理必须调用
span.RecordError(err),不能只打日志——否则 status 不会标为Error - 避免在 defer 里调用
span.End()前修改属性,某些异步场景下 span 可能已结束
分布式追踪最难的从来不是接入,而是让每个环节都守规矩:上游传 context、下游读 context、exporter 不丢数据、collector 不限流、UI 能按 service.name + operation.name 快速筛选。漏掉任意一环,trace 就断成碎片。










