OpenTelemetry在Go微服务中需手动组装exporter、collector等组件才能落地,否则span会静默丢失;根本原因是默认stdoutexporter仅输出日志而不发网络请求,必须显式配置远程exporter(如otlptracehttp或jaeger)并确保OTEL_EXPORTER_OTLP_ENDPOINT指向有效collector地址,同时注意协议(HTTP 4318/gRPC 4317)、collector receiver配置匹配及K8s中DNS和服务发现正确性。

OpenTelemetry 在 Go 微服务中不是“开箱即用”的可观测性平台,它只提供采集能力;要真正落地,必须自己组装 exporter、collector、后端存储和查询界面——否则你只会看到 spans 丢进黑洞里。
为什么 otel.Tracer 调用成功却收不到数据?
这是最常卡住的第一步:代码里调用了 tracer.Start()、打了 span,但 Jaeger 或 Zipkin 界面空空如也。根本原因不是 SDK 配置错,而是默认导出器(stdoutexporter)只打日志,不发网络请求。
- 确认是否显式注册了远程 exporter,比如
otlptracehttp.New()或jaeger.New(),而不是只调用otel.SetTracerProvider(tp) - 检查
OTEL_EXPORTER_OTLP_ENDPOINT环境变量是否指向运行中的otel-collector(默认是localhost:4318),注意 HTTP 端口(4318)和 gRPC 端口(4317)别混用 - Go SDK 默认启用批量上传(
BatchSpanProcessor),若服务启动后立即退出(比如单元测试或短命命令),span 可能来不及 flush——加defer tp.Shutdown(context.Background())
如何让 http.Server 自动埋点又不污染业务逻辑?
直接在每个 handler 里手写 span := tracer.Start(ctx, "GET /users") 是不可维护的。应该用中间件 + OpenTelemetry 官方插件,但要注意版本对齐和路径标签精度。
- 用
otelhttp.NewHandler()包裹http.ServeMux或单个 handler,它会自动提取traceparent并创建 server span - 避免用
net/http原生Server.Handler直接赋值,否则中间件链断裂;推荐http.Handle("/", otelhttp.NewHandler(mux, "/")) - 默认 path 标签是完整路由(如
/api/v1/users/:id),若用 gorilla/mux 或 chi,需配合WithFilter函数清洗掉参数部分,否则 cardinality 爆炸 - 不要在中间件里重复调用
otelhttp.NewTransport()——那是客户端用的,服务端用NewHandler()
otel-collector 配置里哪些字段真正影响 Go 服务上报?
Go SDK 对 collector 的协议和 pipeline 非常敏感。一个看似无关的配置错误(比如 receivers 和 exporters 协议不匹配),会导致所有 span 静默失败。
立即学习“go语言免费学习笔记(深入)”;
- Go 默认走 OTLP/HTTP(
otlphttpreceiver),collector 配置中必须启用对应 receiver:receivers: { otlp: { protocols: { http: {} } } } - 如果 Go 用的是
otlptracegrpc.New(),collector 就得开 gRPC receiver(端口 4317),且exporters段不能只配logging——至少配一个真实后端如jaeger或zipkin -
service.pipelines.traces.exporters列表必须包含你实际使用的 exporter 名(如["jaeger"]),名字错一个字母,span 就消失 - collector 日志级别设为
debug,搜"Exporting ... spans"和"dropped"关键字,比抓包更快定位断点
为什么本地能查到 trace,上 K8s 就全丢?
本质是网络策略和 DNS 解析问题。Go 应用容器里 localhost 指向自己,不是 collector 所在 Pod。
- 把
OTEL_EXPORTER_OTLP_ENDPOINT改成 collector Service 名,例如http://otel-collector.default.svc.cluster.local:4318 - 确认 collector Service 的 port 名是
http(K8s headless service 或 NetworkPolicy 可能按 port 名过滤) - Go 应用容器里执行
curl -v http://otel-collector.default.svc.cluster.local:4318/version,验证连通性——很多团队跳过这步,直接怀疑 SDK - Collector 的 deployment 要限制资源(尤其内存),否则 OOM 后静默终止,K8s 重启期间所有 trace 丢失,现象就是“间歇性无数据”
真正难的不是接入 OpenTelemetry,而是让 trace、metric、log 三者通过 traceID 对齐,且在高并发下不拖慢服务。Go 的 runtime/metrics 和 log/slog 需手动注入 traceID,这部分没有标准方案,容易各自为政。


















