Spring Boot 3.3.0中如何将主线程MDC传递至Feign线程
解决Spring Boot 3.3.0中Feign线程传递MDC上下文的问题
MDC(映射诊断上下文)基于ThreadLocal实现,跨线程时不会自动传递,因此Feign调用的独立线程无法获取主线程的MDC信息。以下是两种场景下的解决方案:
场景1:同步Feign调用(日志在独立线程输出)
通过自定义Feign请求拦截器和Logger,将主线程的MDC参数传递到Feign请求头,并在日志输出时恢复到Feign线程的MDC中:
1. 实现MDC请求拦截器
将主线程的MDC参数添加到Feign请求头:
import feign.RequestInterceptor; import feign.RequestTemplate; import org.slf4j.MDC; import org.springframework.stereotype.Component; import java.util.Map; @Component public class MdcFeignRequestInterceptor implements RequestInterceptor { private static final String MDC_USER_ID = "userID"; private static final String MDC_FLOW_ID = "flowID"; @Override public void apply(RequestTemplate template) { Map<String, String> mdcContext = MDC.getCopyOfContextMap(); if (mdcContext != null) { mdcContext.forEach((key, value) -> { if (MDC_USER_ID.equals(key) || MDC_FLOW_ID.equals(key)) { template.header(key, value); } }); } } }
2. 实现MDC感知的Feign Logger
在日志输出前从请求头恢复MDC,输出后清理:
import feign.Logger; import feign.Request; import feign.Response; import org.slf4j.MDC; import org.springframework.util.StringUtils; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Collections; public class MdcAwareFeignLogger extends Logger { private static final String MDC_USER_ID = "userID"; private static final String MDC_FLOW_ID = "flowID"; @Override protected void logRequest(String configKey, Level logLevel, Request request) { Map<String, String> mdcMap = new HashMap<>(MDC.getCopyOfContextMap() != null ? MDC.getCopyOfContextMap() : Collections.emptyMap()); String userId = request.headers().getOrDefault(MDC_USER_ID, Collections.singletonList("")).get(0); String flowId = request.headers().getOrDefault(MDC_FLOW_ID, Collections.singletonList("")).get(0); if (StringUtils.hasText(userId)) mdcMap.put(MDC_USER_ID, userId); if (StringUtils.hasText(flowId)) mdcMap.put(MDC_FLOW_ID, flowId); MDC.setContextMap(mdcMap); super.logRequest(configKey, logLevel, request); } @Override protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException { Request request = response.request(); Map<String, String> mdcMap = new HashMap<>(MDC.getCopyOfContextMap() != null ? MDC.getCopyOfContextMap() : Collections.emptyMap()); String userId = request.headers().getOrDefault(MDC_USER_ID, Collections.singletonList("")).get(0); String flowId = request.headers().getOrDefault(MDC_FLOW_ID, Collections.singletonList("")).get(0); if (StringUtils.hasText(userId)) mdcMap.put(MDC_USER_ID, userId); if (StringUtils.hasText(flowId)) mdcMap.put(MDC_FLOW_ID, flowId); MDC.setContextMap(mdcMap); try { return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime); } finally { MDC.clear(); } } }
3. 更新Feign配置类
注册自定义拦截器和Logger:
@Configuration @EnableFeignClients(basePackages = {"client.package"}) public class FeignConfig { @Bean public CustomFeignRequestLogging customFeignRequestLogging() { return new CustomFeignRequestLogging(); } @Bean Logger.Level feignLoggerLevel() { return Logger.Level.BASIC; } @Bean public Logger feignLogger() { return new MdcAwareFeignLogger(); } @Bean public MdcFeignRequestInterceptor mdcFeignRequestInterceptor() { return new MdcFeignRequestInterceptor(); } }
场景2:异步Feign调用
如果使用异步Feign调用,需要通过线程池的TaskDecorator传递MDC上下文:
1. 配置带MDC传递的线程池
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.slf4j.MDC; import java.util.Map; @Configuration public class FeignAsyncPoolConfig { @Bean(name = "feignAsyncExecutor") public ThreadPoolTaskExecutor feignAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(20); executor.setThreadNamePrefix("FeignAsync-"); // 装饰器传递MDC executor.setTaskDecorator(runnable -> { Map<String, String> mdcContext = MDC.getCopyOfContextMap(); return () -> { try { if (mdcContext != null) { MDC.setContextMap(mdcContext); } runnable.run(); } finally { MDC.clear(); } }; }); executor.initialize(); return executor; } }
2. 定义异步Feign客户端
@FeignClient( name = "client", url = "${client.url}", fallbackFactory = ClientFallbackFactory.class, configuration = FeignConfig.class ) public interface AsyncClient { @GetMapping(value = "${client.path}") CompletableFuture<Optional<String>> getObjsAsync( @RequestParam("id") Integer id, @RequestParam("boolParm") Boolean boolParm, @RequestParam("appendix") Integer appendix ); }
3. 配置异步Feign构建器
import feign.AsyncFeign; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration public class AsyncFeignConfig { private final ThreadPoolTaskExecutor feignAsyncExecutor; public AsyncFeignConfig(ThreadPoolTaskExecutor feignAsyncExecutor) { this.feignAsyncExecutor = feignAsyncExecutor; } @Bean public AsyncFeign.Builder asyncFeignBuilder() { return AsyncFeign.builder().executor(feignAsyncExecutor); } }
完成以上配置后,Feign线程的日志就能包含主线程的MDC信息,实现日志链路追踪。
内容的提问来源于stack exchange,提问作者valerio iorio
相关产品推荐
相关产品推荐

