http handler未用otelhttp.newhandler包裹导致链路在入口中断;goroutine中须用trace.contextwithspan而非context.withvalue传递span;grpc需客户端和服务端均配置otelgrpc拦截器;tracer应按服务命名且延迟初始化。

HTTP服务端断链:没用 otelhttp.NewHandler 包裹 handler 就等于没接入
90% 的链路断在入口,不是 exporter 配错了,而是 HTTP handler 没被 otelhttp.NewHandler 包裹。它不光创建 span,还做三件事:从 traceparent 头提取 parent span、把 span 注入 request context、在 response 写回 trace headers。漏掉任意一步,下游就收不到 trace context。
-
http.HandlerFunc必须先转成http.Handler才能传给otelhttp.NewHandler,例如:otelhttp.NewHandler(http.HandlerFunc(yourHandler), "api/v1/users") - 别自己写中间件去读
r.Header.Get("traceparent")—— 它不校验格式、不处理大小写、不兼容多值,propagators.Extract才是标准解法,而otelhttp.NewHandler已内置 - 如果你用 Gin/Echo,不要只加
otelgin.Middleware就以为万事大吉;确认它底层是否真调用了otelhttp.NewHandler,有些旧版本 contrib 包只是简单 wrapper,没做 Extract/Inject
goroutine 中 span 丢失:用 context.WithValue 存 span 是无效的
跨 goroutine 时,span 不会自动传递,但错误做法是 ctx = context.WithValue(ctx, spanKey, span)。OpenTelemetry 的关联依赖的是 trace.SpanContext 在 context 中的固定 key(trace.ContextKey),不是任意 key。用错 key,下游 trace.SpanFromContext(ctx) 永远返回 nil。
- 必须用
trace.ContextWithSpan(ctx, span)注入,它把SpanContext写进标准位置 - 启动 goroutine 时,传入的是带 span 的 ctx,不是原始 request ctx:
go doWork(trace.ContextWithSpan(ctx, span)) - 数据库查询、RPC 调用、定时任务等异步场景最容易踩这个坑;尤其注意
sql.DB.QueryContext这类方法,必须传入已注入 span 的 ctx
gRPC 调用不续链:otelgrpc 客户端和服务端都要配拦截器
HTTP 和 gRPC 的传播机制完全不同。otelhttp 对 gRPC 请求完全无效。只配 client 端拦截器,请求发出去时仍不带 traceparent;只配 server 端,收到的 metadata 里也没有 trace context。两边缺一不可。
- 客户端:用
otelgrpc.WithClientTrace包装grpc.Dial,确保 outbound metadata 注入 - 服务端:用
otelgrpc.UnaryServerInterceptor或otelgrpc.StreamServerInterceptor包裹grpc.NewServer - 别混用
otelhttp.Transport和 gRPC —— 它只管 HTTP 出向,对 gRPC 无感知
otel.Tracer 名称和初始化时机:全局变量缓存 tracer 导致 span 丢或采样失效
otel.Tracer("default") 不是线程安全问题,而是语义隔离失效。所有模块共用一个名称,span name 冲突、属性覆盖、采样策略无法按服务区分。更危险的是:在 SDK 初始化前就调用 otel.Tracer,拿到的是 noop 实现,埋点全丢。
- 每个服务/模块用专属名:
otel.Tracer("user-service")、otel.Tracer("payment-client") - tracer 实例不要存在包级变量里;应在
main()初始化完sdktrace.TracerProvider后,再通过函数获取,例如GetTracer("user-service") - 生产环境禁用
sdktrace.AlwaysSample();改用sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.01)),否则高 QPS 下 exporter 积压、重试风暴、CPU 拉满
trace.ContextWithSpan),一次是跨进程(靠 header/metadata + propagator)。少做一次,链就断了。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











