Kratos 中 TraceID 透传需显式通过 context.Context 实现,必须手动解析请求头(如 X-Trace-ID 或 traceparent)、注入 context、启用 tracing.Server/tracing.Client 中间件,并用 log.WithContext() 绑定日志,四步缺一不可。

TraceID 透传必须走 context.Context,不是靠 HTTP Header 自动生效
Kratos 默认不会自动从 X-Trace-ID 或 Traceparent 头里提取并注入到当前 context 中。你看到请求头里有 Trace ID,但日志里没打出来、下游服务收不到——大概率是漏了中间件初始化或上下文未正确传递。
关键点在于:Kratos 的 tracing 和 metadata 都依赖 context.WithValue() 向下透传,而这个动作必须显式触发。
- HTTP 请求进来的第一层(比如
http.Serverhandler)需要手动从r.Header.Get("X-Trace-ID")或otel.GetTextMapPropagator().Extract()拿到 trace 上下文,并塞进context - gRPC 场景下同理,需在
UnaryInterceptor里调用otel.GetTextMapPropagator().Extract(),再用grpc.SetFullMethod等辅助填充 span 名称 - 如果你用了 Kratos 内置的
tracing.Server中间件(如tracing.Server(tracing.WithTracerProvider(tp))),它只负责创建 span,不负责解析入参 header —— 解析得你自己写中间件补上
日志中打印 TraceID 要绑定 logger.WithContext(),不是改格式字符串
Kratos 的 log.Logger 是 context-aware 的,但默认不读取 trace 上下文。直接在日志里写 "trace_id": ctx.Value("trace_id") 是错的 —— 这个值根本没被 set 过,ctx 里也没有 magic key。
正确做法是让 logger 主动从 OpenTelemetry 的 propagators 里提取当前 span 的 trace ID,并作为字段注入:
- 初始化 logger 时用
log.WithContext(ctx)包一层,确保后续所有logger.Info()都携带当前 context - 在日志 encoder(比如
log.DefaultLogger的 JSON encoder)里,调用trace.SpanFromContext(ctx).SpanContext().TraceID().String()获取 trace ID 字符串 - 不要自己往 context 里塞
"trace_id"键 —— OpenTelemetry 的 span 已经封装好了,重复存会导致不一致
tracing.Client 和 tracing.Server 中间件必须成对启用,否则下游收不到 span
只在服务端加 tracing.Server,客户端不加 tracing.Client,那整个链路就断在第一个 RPC 调用处。Kratos 的 tracing 不是全局开关,而是每个 transport 层单独控制的。
- HTTP 客户端调用下游:需在
http.Client的 middleware 链里插入tracing.Client(),它会自动把当前 span 注入traceparent头 - gRPC 客户端调用:同样要 wrap
grpc.Dial的grpc.WithUnaryInterceptor(tracing.Client()) - 注意
tracing.Client()依赖当前 context 里已有活跃 span;如果上游没传或你没手动 StartSpan,它会 fallback 到 noop span,下游就收不到有效 trace 数据
自定义中间件提取 X-Trace-ID 时,别忽略大小写和空格
有些网关(比如 Nginx、Kong)转发时会把 header 名转成小写,或者前后带空格。直接 r.Header.Get("X-Trace-ID") 可能返回空字符串,导致 trace 断裂。
- 建议统一用
strings.TrimSpace(r.Header.Get("x-trace-id"))或更稳妥地遍历r.Header找匹配^x[-_]?trace[-_]?id$的 key - 如果用的是 W3C
traceparent格式(如00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01),必须用otel.GetTextMapPropagator().Extract()解析,不能字符串截取 - 生成新 trace ID 的时机只能在入口(如 HTTP handler 第一行或 gRPC interceptor 最早位置),之后所有子 span 都应复用该 context,避免多次
StartSpan导致父子关系错乱
tracing.Server 就万事大吉,其实它连 header 都不碰。


















