根本原因是 handler 链路断裂——otelhttp.newhandler 包裹后的 handler 未被正确注册或被中间件覆盖,导致 server 端 span 无法生成。

为什么 otelhttp.NewHandler 包裹的 HTTP handler 不上报 span?
常见现象是服务启动后没任何 trace 数据,或只看到 client 端 span、看不到 server 端 span。根本原因往往是 handler 链路断了——otelhttp.NewHandler 返回的新 handler 没被真正注册进路由,或者被中间件顺序覆盖了。
- 确保你用的是
otelhttp.NewHandler包裹后的 handler,而不是原始 handler;比如http.Handle("/api", otelhttp.NewHandler(myHandler, "api")),不是http.Handle("/api", myHandler) - 如果你用的是
chi、gin等框架,别直接传http.Handler,得用对应适配器:chi.WrapHandler或gin.WrapH(otelhttp.NewHandler(...)) - 检查是否在 handler 执行前就返回了(如 panic、early return、超时中断),span 会在 defer 的
span.End()中结束,提前退出会导致 span 被丢弃 - 确认
otelhttp.NewHandler的第二个参数(span name)不是空字符串,否则部分 exporter(如 Jaeger)会忽略该 span
如何让 context 里的 traceID 穿透到 goroutine 和数据库调用?
Go 的 context 是链路追踪的载体,但默认 goroutine 不继承父 context,DB 驱动也不自动读取 context 中的 span。不显式传递,trace 就断了。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 所有新起 goroutine 必须显式传入带 span 的 context:
go doWork(ctx),而不是go doWork() - 数据库操作必须用带 context 的方法:用
db.QueryContext(ctx, ...)替代db.Query(...);ORM 如gorm要启用WithContext(ctx)链式调用 - 日志打点如果想关联 traceID,别用全局 logger,改用
log.WithValues("trace_id", trace.SpanContext().TraceID().String()),或集成otellogrus这类桥接器 - 注意:
context.Background()或context.TODO()会彻底切断链路,生产代码里禁止出现
TracerProvider 和 Resource 初始化顺序写反会怎样?
OpenTelemetry Go SDK 要求 Resource 必须在创建 TracerProvider 前就准备好,并作为选项传入。写反了不会报错,但 service.name、host.ip 等关键属性全丢失,后台(如 Tempo、Jaeger)无法按服务维度聚合数据。
- 正确顺序:
resource.MustNewWithAttributes(...)→otlphttp.NewClient(...)→sdktrace.NewTracerProvider(..., sdktrace.WithResource(res)) -
Resource至少包含service.name,建议加上service.version和telemetry.sdk.language(值为"go") - 不要在多个地方重复调用
otel.SetTracerProvider,它只会生效第一次;多模块共用一个 provider,避免内存泄漏和 span 丢失 - 如果用
OTEL_RESOURCE_ATTRIBUTES环境变量注入,也要确保它在TracerProvider初始化前已加载(比如 main 函数开头就os.Setenv不可靠,应由部署层注入)
为什么本地跑通了,上 K8s 就看不到 trace?
典型表现:本地 curl localhost:8080 能查到 trace,K8s 里 cURL ingress 地址却无数据。问题不在代码,而在网络和配置漏项。
- 确认 collector 地址可连通:
telnet otel-collector.default.svc.cluster.local 4318(HTTP endpoint)或4317(gRPC),K8s DNS 解析失败是高频原因 - 检查 collector 接收端口是否暴露:
OTEL_EXPORTER_OTLP_ENDPOINT应设为http://otel-collector.default.svc.cluster.local:4318,不是localhost:4318 - 确认 collector 配置支持接收(比如
otlphttpreceiver 是否启用,exporters是否配了logging或jaeger用于调试) - Pod 内 DNS 缓存可能延迟,加个
sleep 2 && curl -v http://otel-collector...在 initContainer 里验证更稳
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










