traceparent丢失表现为Jaeger中链路中断、后续服务无Span;定位需检查otelhttp.NewHandler是否包裹最终handler、gRPC客户端是否配置UnaryClientInterceptor、HTTP请求头是否含且正确传递traceparent。

traceparent 丢失的典型表现和定位方法
跨服务调用后链路在某一级突然中断,Jaeger 里只看到前半段 Span,后续服务完全没记录——这基本是 traceparent 没传下去。Kratos 默认不自动注入/提取该 Header,得靠 OpenTelemetry 中间件显式处理。
常见错误现象:otelhttp.NewHandler 挂载位置不对、gRPC 客户端漏了 otelgrpc.UnaryClientInterceptor、或者手动拼接 HTTP 请求时根本没转发 Header。
- 检查 HTTP Server 是否用
otelhttp.NewHandler包裹(不是手写span.Start),且包裹的是最终 handler,不是中间某一层 - 确认 gRPC Client 初始化时传了
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()) - 若 service 内部用
http.DefaultClient调下游,必须改用otelhttp.NewClient,否则 trace context 不会自动注入 - 用 curl 或 Wireshark 抓包看请求头:上游发出的请求是否含
traceparent,下游收到的请求头里有没有它
异步 Goroutine 中 Span 消失怎么补救
Kratos 里常在 service 层起 Goroutine 做日志上报、缓存刷新等事,但新协程默认拿不到父 Span,导致链路断成两截。这不是 Kratos 的 bug,而是 context 未显式传递的通用 Go 问题。
错误写法:go func() { /* 无 ctx 传入,span 为空 */ }();正确做法是把带 Span 的 context.Context 显式传进去。
- 启动 Goroutine 前,用
trace.ContextWithSpan把当前 span 注入 ctx:ctx, _ := trace.ContextWithSpan(ctx, span) - 再把该 ctx 传给异步函数:
go doAsyncWork(ctx) - 避免用
context.Background()或context.TODO()启动协程,它们彻底脱离链路 - 如果异步任务需独立生命周期(如后台定时同步),可考虑新建 Span 并设 parent 为当前 Span:
tracer.Start(ctx, "async-task", trace.WithNewRoot())
OTLP 导出失败导致链路“看不见”而非“断掉”
链路其实没断,只是数据没发到 Jaeger —— 这类问题最容易误判为传播失败。典型症状是:本地 curl 能看到 traceparent,Jaeger 界面却空空如也,日志也没报错。
根本原因集中在 OTLP 导出器配置与网络连通性上,尤其容器环境。
- Docker 内服务连
localhost:4317是连自己,不是宿主机 Jaeger;Mac/Windows 改用host.docker.internal:4317,Linux 改用宿主机真实 IP - 确认 Jaeger 镜像启用了 OTLP:
docker run -e COLLECTOR_OTLP_ENABLED=true -p 4317:4317 ...,否则 4317 端口不监听 - 导出器未设超时,网络抖动时静默失败;建议加
otlptracegrpc.WithDialOption(grpc.WithBlock(), grpc.WithTimeout(5*time.Second)) - Kratos 的
app.Run生命周期中,TracerProvider必须在app.WithServer之前初始化,否则 server 启动时 tracer 还没 ready
中间件挂载顺序影响 Span 创建时机
Kratos 的 HTTP Server 支持多层中间件,但 OpenTelemetry 必须是第一层(最外层),否则 request 进来时还没创建 root span,后续所有子 Span 都会丢失 parent。
比如你写了 auth 中间件校验 token,又写了 otel 中间件埋点,如果 auth 在前、otel 在后,那 auth 里的逻辑就完全不在任何 Span 下,链路从 auth 就开始“少一截”。
- 在
internal/server/http.go中,确保otelhttp.NewHandler是最外层包装,即直接 wrapmux,而不是 wrap 某个子路由 - 不要把 otel 中间件塞进
service层的 handler 里,那里已经晚了 - gRPC Server 同理:
otelgrpc.UnaryServerInterceptor必须作为第一个 interceptor 传入grpc.UnaryInterceptor链 - 验证方式:在第一个 handler 里打日志输出
span.SpanContext().TraceID().String(),看是否为空
Kratos 不提供“自动链路修复”,所有传播、导出、挂载都依赖开发者对 OpenTelemetry 的精确控制。最容易被忽略的是:HTTP 和 gRPC 的传播机制完全不同,不能只配一边;还有就是异步场景下 context 传递,它不像同步调用那样天然延续。


















