You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 15:05:53