发布于2026-07-19 阅读(0)
扫一扫,手机访问
先说几个核心判断,都是一线踩坑换来的经验:otel.Tracer 不能在包级或 init() 里提前调用,否则返回的是 noop tracer,埋了也白埋,Span 全部丢光;HTTP 中间件必须手动 Extract/Inject traceparent,别指望 context.WithValue 能帮你传;生产环境千万别用 sdktrace.AlwaysSample(),老老实实用 ParentBased 采样器。下面逐个拆开聊。

OpenTelemetry 的 Tracer 实例必须绑定到已初始化的 TracerProvider。如果在全局变量、init() 或 SDK 初始化前就调用 otel.Tracer("my-service"),它会返回一个不干活的 noop tracer —— 埋点无报错,但 Span 全部静默丢失。这问题在线上特别隐蔽,你可能会发现:SpanContext.SpanID() 返回全零、Jaeger/OTLP 后端收不到任何数据、span.End() 后既没日志也没错误。
正确的做法是封装一个 GetTracer() 函数,在 main() 完成 otel.SetTracerProvider(tp) 后首次调用它。别信文档里那些“先声明再用”的示例——那些示例默认你已确保 SDK 就绪;实际项目里必须加校验,比如 if tp := otel.GetTracerProvider(); tp != nil { ... },这步不能省。
Go 标准 http.ServeMux 和主流 Web 框架(Gin、Echo)都不自动解析 traceparent 头,也不会把提取出的 context 注入到 handler 的 context.Context 中。不手动处理,链路就在第一个服务断掉,后续全部失联。
具体操作上,必须用 otel.GetTextMapPropagator().Extract(req.Context(), propagation.HeaderCarrier(req.Header)),而不是 req.Header.Get("traceparent") 然后自己 parse。Gin 用户尤其要注意:c.Request.Context() 是只读副本,得用 c.Request = c.Request.WithContext(newCtx) 覆盖,后续 handler 才能拿到带 trace 的 context。另外,别漏掉设置 Span name:推荐用 req.Method + " " + req.URL.Path,否则所有 HTTP Span 都叫 HTTP GET,失去区分度,排查问题时会很头疼。
生产环境最容易被忽略的是采样器和传播器的初始化顺序。这两处一错,整条链路就断在第一个服务里,后端收不到任何数据。
sdktrace.AlwaysSample() 在生产环境很危险:QPS 上千时 exporter 积压、超时、重试风暴,可能拖垮服务 CPU 和网络。推荐用 sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1)) —— 注意:它是按 trace ID 哈希后取 10%,不是每 10 个请求采 1 个;低频服务可能连续几小时没数据,上线前务必验证。
传播器(propagator)必须在 otel.SetTracerProvider() 之后、中间件启动前注册,例如:otel.SetTextMapPropagator(propagation.TraceContext{}),否则 Extract/Inject 会退化为 noop。事情说清了就结束,这些细节决定了你的链路是否能真正跑通。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8