Feign调用丢失TraceId的根本原因是MDC基于ThreadLocal无法跨线程传递,且拦截器仅作用于当前线程;必须通过RequestInterceptor注入X-Trace-Id头,并在服务端Filter中提取绑定至MDC,异步场景需结合TTL保障透传。

只要 Feign 请求走的是标准 HTTP 调用、且服务端能从 Header 中正确提取并绑定到当前线程,TraceId 就能全自动透传——但前提是拦截器不能漏掉关键 Header,也不能在异步或线程池场景下失效。
Feign RequestInterceptor 必须注入哪些 Header
TraceId 透传依赖服务端从请求头中读取并续传上下文。Spring Cloud Sleuth 默认使用 trace-id 和 span-id(小写连字符),而 SkyWalking 使用 sw8;若未启用 Sleuth,需统一约定字段名,常见选择是 X-Trace-Id。
- 必须透传的 Header:
X-Trace-Id(或trace-id)、X-Span-Id(可选,用于更细粒度追踪) - 避免重复注入:如果已引入
spring-cloud-starter-sleuth,它会自动注册自己的RequestInterceptor,此时手动再加一个同名拦截器可能导致 Header 冲突或覆盖 - 注意大小写:HTTP Header 名称不区分大小写,但某些网关(如 Spring Cloud Gateway)或中间件(如 Nginx)可能对下划线或连字符敏感,建议统一用
X-Trace-Id - 不要只传 MDC 值:
MDC.get("traceId")可能为空——必须确保上游已生成并存入,否则拦截器里取不到就等于白配
为什么 Feign 拦截器在异步调用中会丢失 TraceId
因为 RequestInterceptor.apply() 是在 Feign 发起 HTTP 请求前执行的,它只作用于当前线程;一旦调用被提交到线程池(比如 @Async 方法内调用 FeignClient),新线程的 MDC 或 ThreadLocal 是空的,拦截器拿不到原始 TraceId。
- 根本原因:MDC 基于
ThreadLocal,不具备跨线程传递能力 - 解决方案不是改拦截器,而是提前把 TraceId “带过去”:在提交任务前,用
TransmittableThreadLocal包装MDC,或使用TtlRunnable/TtlCallable包裹异步逻辑 - Feign 自身不处理线程切换,所以
RequestInterceptor无法感知下游是否异步——它只忠实地运行在调用线程上 - 典型错误写法:
@Async void doSomething() { feignClient.call(); }→ 此时feignClient.call()执行在线程池线程,MDC.get("X-Trace-Id")为 null
如何验证 TraceId 是否真正透传成功
不能只看 Feign 客户端发出去的请求头有没有,更要确认服务端是否成功解析并绑定到日志上下文——否则只是“看起来有”,实际链路断在第二跳。
- 在被调用服务的 Controller 入口打日志:
log.info("Received trace: {}", MDC.get("X-Trace-Id"));,检查是否非 null - 检查服务端是否配置了对应的 Header 提取逻辑:Sleuth 自动做,自定义方案需在
OncePerRequestFilter中手动从request.getHeader("X-Trace-Id")取值并塞入MDC - 观察日志输出格式:Logback 配置中必须包含
%X{X-Trace-Id}或%X{traceId},否则即使 MDC 有值,日志里也看不到 - 注意 Feign fallback 场景:熔断或降级时,
fallbackFactory创建的 fallback 实例默认不在原线程执行,TraceId 同样会丢失,需在 fallback 内手动恢复
最易被忽略的一点:网关层(如 Spring Cloud Gateway)若没做 Header 透传配置,所有下游服务看到的都是空 TraceId——它不会自动转发你定义的 X-Trace-Id,必须显式在路由配置中添加 addRequestHeader 或自定义 GlobalFilter。










