Kotlin开发的Spring WebFlux应用中MDCContext在调用Mono.deferContextual获取Reactor Context时丢失的问题
Kotlin开发的Spring WebFlux应用中MDCContext在调用Mono.deferContextual获取Reactor Context时丢失的问题
我之前在维护Kotlin版WebFlux应用时也碰到过一模一样的问题,其实核心原因是Reactor Context的合并逻辑没处理对,咱们一步步拆解解决:
问题根源
WebFlux里的MDC是通过MDCContext这个Reactor Context条目来传递的,它本质是把线程局部的MDC快照存到了Context里。如果你的WebFilter直接替换了整个Context而不是合并原有内容,就会把默认的MDCContext给覆盖掉——这就是你在Mono.deferContextual里拿不到它的主要原因。
解决方案步骤
1. 修复WebFilter的Context写入逻辑
别用subscriberContext(Context.of(...))直接替换Context,改用contextWrite来合并原有Context与自定义条目,这样就能保留原有的MDCContext:
import org.springframework.web.server.WebFilter import org.springframework.web.server.WebFilterChain import org.springframework.web.server.ServerWebExchange import reactor.core.publisher.Mono import reactor.util.context.MDCContext import java.util.UUID class CustomTraceIdWebFilter : WebFilter { override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> { val traceId = UUID.randomUUID().toString() // 用contextWrite合并原有Context,同时添加自定义条目到Context和MDC return chain.filter(exchange) .contextWrite { existingContext -> // 1. 把自定义traceId加入Reactor Context val contextWithTraceId = existingContext.put("traceId", traceId) // 2. 同步更新MDCContext,确保日志能读取到 val currentMdc = existingContext.getOrDefault(MDCContext::class.java, MDCContext.empty()) val updatedMdc = MDCContext(currentMdc.contextMap + mapOf("traceId" to traceId)) contextWithTraceId.put(MDCContext::class.java, updatedMdc) } } }
2. 在Mono.deferContextual中正确获取MDCContext
在deferContextual的lambda里,你可以直接从当前Context中取出MDCContext,或者读取你需要的MDC条目——别依赖线程局部的MDC静态方法,WebFlux中线程是复用的,线程局部变量不可靠:
import org.slf4j.LoggerFactory import reactor.core.publisher.Mono import reactor.util.context.MDCContext private val log = LoggerFactory.getLogger("YourServiceClass") fun processRequest(): Mono<String> { return Mono.deferContextual { context -> // 方式1:直接从Context获取自定义traceId val traceId = context.getOrDefault("traceId", "unknown-trace-id") // 方式2:取出完整MDCContext,读取所有MDC条目 val mdcContext = context.getOrDefault(MDCContext::class.java, MDCContext.empty()) val requestId = mdcContext.contextMap["requestId"] ?: "unknown-request-id" // 后续操作会自动继承当前Context,包括MDCContext Mono.just("Processed request with traceId: $traceId, requestId: $requestId") .doOnNext { log.info("{}", it) } // 日志框架会自动从MDCContext取值 } }
3. 确保日志框架配置正确
以Logback为例,在日志格式中直接引用MDC条目即可:
<!-- logback.xml 示例 --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg - traceId=%X{traceId}%n</pattern> </encoder> </appender>
额外注意点
- 避免在WebFlux中直接使用
MDC.put()/MDC.get()这类静态方法,除非在doOnEach或contextWrite的范围内操作,否则线程复用会导致MDC值混乱。 - 如果用了
subscribeOn或publishOn切换线程,不用担心Context丢失——Reactor会自动把Context传递到新线程的订阅链中。
内容来源于stack exchange
相关产品推荐
相关产品推荐

