根本原因是未调用otel.settracerprovider()或exporter配置失败,导致tracing.server()使用nooptracerprovider静默丢弃所有span;必须在main()最开始注册真实tracerprovider并绑定可用exporter。

根本原因不是 Kratos 不支持 OpenTelemetry,而是 trace 数据在创建后被静默丢弃——90% 的“没数据”问题出在 otel.SetTracerProvider() 没调用或 exporter 配置失败。
为什么 tracing.Server() 启用了却看不到任何 span
因为 tracing.Server() 默认使用 NoopTracerProvider,它不发数据、不报错、不打日志,只安静地把所有 span 丢进黑洞。这不是 bug,是设计上的安全兜底。
- 必须在
main()最早位置(早于任何 server 初始化)调用otel.SetTracerProvider(tp),其中tp是你构造的真实TracerProvider -
TracerProvider必须绑定一个可用的 exporter,比如otlphttp.NewClient()或jaeger.NewThriftUDPExporter() - exporter 地址写错、端口不通、协议不匹配(如用 OTLP/gRPC client 连 OTLP/HTTP endpoint)都会静默失败,
otel默认不输出连接错误日志 - 检查方式:在 exporter 构建后加一行
log.Printf("exporter ready: %+v", exp),确认对象非 nil;再用telnet或curl -v手动测 endpoint 连通性
tracing.Server() 和 tracing.Client() 的 filter 行为差异
两者都接受 tracing.WithFilter(),但语义完全不同,混用会导致断链或漏采。
-
tracing.Server()的 filter 控制「是否为该请求创建 span」:适合跳过/healthz、/metrics等无业务意义路径 -
tracing.Client()的 filter 控制「是否向下游注入 trace header」:适合跳过对第三方 API(如支付网关)的 outbound 调用,避免污染外部链路 - 若 client filter 返回 false,span 仍会创建,但不会携带
traceparentheader,下游服务收不到上下文,链路就此断裂 - 常见误配:给 client 传了 server 的 filter 函数,结果所有 outbound 请求都不带 trace header
高负载下 span 丢失或延迟上报的排查点
不是中间件或 exporter 配置错了,而是资源竞争或缓冲区溢出导致数据被 drop。
-
OTLP exporter默认使用内存队列(WithQueue()),队列满时新 span 会被直接丢弃,且无 warning 日志 - 检查是否启用了采样器:
ParentBased(TraceIDRatioSampler(0.01))比AlwaysSample()更稳妥;Kratos 2.8.2 及以上默认仍是AlwaysSample - gRPC exporter 在节点宕机后可能因连接未及时关闭而阻塞(见 OpenTelemetry Collector 0.73.0 已知问题),建议升级到 0.90.0+ 或改用 HTTP exporter
- 若用 Jaeger Thrift UDP,注意 UDP 包大小限制(通常 65507 字节),大 span 可能被截断或丢弃,可改用
jaeger.NewThriftHTTPExporter()
最容易被忽略的是:Kratos 不自动初始化全局 tracer provider,这一步必须由你亲手控制,且必须在任何 tracing.Server() 或 tracing.Client() 实例化之前完成——哪怕只差一行代码顺序,trace 就永远发不出去。











