Spring Integration应用发起HTTP调用后日志丢失correlationId问题
问题根本原因
MDC(Mapped Diagnostic Context)底层基于ThreadLocal实现,上下文仅绑定到当前执行线程:
- 入站请求的过滤器、转换器、首次POST HTTP出站调用都和原始web请求线程同链路,MDC上下文正常存在,日志可以正常打印字段
- Spring Integration的
int:delayer组件默认会将后续消息流程提交到内置的调度线程池执行,原请求线程在消息被delayer挂起后就会被归还到web线程池,新调度线程不会自动继承原线程的MDC内容,因此delayer之后的流程MDC为空,correlationId丢失。
解决方案
推荐优先选择全局消息头传递+通道拦截器的方案,可覆盖所有异步组件的MDC透传场景:
方案1:消息头携带上下文 + 全局通道拦截器透传MDC
该方案不依赖特定组件的线程配置,对所有Spring Integration异步组件生效:
- 首先配置HTTP入站网关,将请求头
x-correlation-id映射为消息头,保证上下文跟随消息全链路传递:
<int-http:inbound-gateway id="httpInboundGateway" mapped-request-headers="x-correlation-id" <!-- 其余原有配置保持不变 --> ... />
- 实现MDC传播通道拦截器,在消息进入通道处理前将消息头中的correlationId写入当前线程MDC,处理完成后清除:
import org.slf4j.MDC; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.ChannelInterceptor; public class MdcPropagationInterceptor implements ChannelInterceptor { private static final String CORRELATION_ID_HEADER = "x-correlation-id"; @Override public Message<?> preSend(Message<?> message, MessageChannel channel) { Object correlationId = message.getHeaders().get(CORRELATION_ID_HEADER); if (correlationId != null) { MDC.put("correlationId", correlationId.toString()); } return message; } @Override public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) { MDC.remove("correlationId"); } }
- 配置全局通道拦截器,对所有通道生效:
<int:channel-interceptor pattern="*" order="-1"> <bean class="com.yourpackage.MdcPropagationInterceptor"/> </int:channel-interceptor>
方案2:自定义MDC感知的调度器,仅针对delayer组件生效
如果仅需要处理delayer场景,可重写调度器实现线程切换时的MDC复制:
- 实现支持MDC透传的调度线程池:
import org.slf4j.MDC; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import java.util.Map; import java.util.concurrent.ScheduledFuture; public class MdcAwareTaskScheduler extends ThreadPoolTaskScheduler { @Override public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) { // 提前拿到原线程的MDC上下文 Map<String, String> mdcContext = MDC.getCopyOfContextMap(); return super.schedule(() -> { // 新线程执行前绑定MDC if (mdcContext != null) { MDC.setContextMap(mdcContext); } try { task.run(); } finally { // 执行完清除MDC,避免线程复用污染上下文 MDC.clear(); } }, trigger); } // 按需重写其余schedule相关方法,逻辑和上面一致 }
- 将自定义调度器配置给delayer组件:
<bean id="mdcAwareScheduler" class="com.yourpackage.MdcAwareTaskScheduler"/> <int:delayer id="yourDelayer" default-delay="1000" task-scheduler="mdcAwareScheduler" <!-- 其余原有配置保持不变 --> ... />
方案3:使用Spring Integration内置上下文传播(版本要求5.2+)
高版本Spring Integration已内置MDC传播能力,直接配置内置拦截器即可:
<bean id="contextPropagatingInterceptor" class="org.springframework.integration.context.ContextPropagatingChannelInterceptor"> <property name="threadLocalAccessor"> <bean class="org.springframework.integration.context.MdcThreadLocalAccessor"> <property name="keys" value="correlationId"/> </bean> </property> </bean> <int:channel-interceptor pattern="*" ref="contextPropagatingInterceptor" order="-1"/>
内容的提问来源于stack exchange,提问作者prudhvin000
相关产品推荐
相关产品推荐

