Logback中如何在StructuredTaskScope的fork任务中传递MDC值?
在Java 21+的StructuredTaskScope中传递MDC上下文
核心思路
借助Java 21的ScopedValue作为MDC上下文的载体——它会自动被StructuredTaskScope的fork任务继承,再通过自定义Logback的MDCAdapter让日志框架直接读取这个ScopedValue中的上下文,实现MDC跨任务的自动传递。
步骤1:用ScopedValue绑定MDC上下文
先定义全局的ScopedValue存储MDC键值对,再在StructuredTaskScope内部绑定当前线程的MDC上下文:
import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.StructuredTaskScope; import java.util.function.Supplier; import org.slf4j.MDC; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ResponseHandler { private static final Logger logger = LoggerFactory.getLogger(ResponseHandler.class); // 用于承载MDC上下文的ScopedValue private static final ScopedValue<Map<String, String>> MDC_CONTEXT = ScopedValue.newInstance(); public Response handle() throws ExecutionException, InterruptedException { // 捕获当前线程的MDC快照 Map<String, String> currentMdc = MDC.getCopyOfContextMap(); try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { // 绑定ScopedValue,后续fork的所有任务都会继承这个MDC上下文 ScopedValue.where(MDC_CONTEXT, currentMdc).run(() -> { Supplier<String> user = scope.fork(() -> { logger.info("1"); return "user_123"; }); Supplier<Integer> order = scope.fork(() -> { logger.info("2"); return 456; }); scope.join().throwIfFailed(); logger.info("3"); }); return new Response(user.get(), order.get()); } } // 示例Response类 public record Response(String user, Integer order) {} }
步骤2:自定义ScopedMDCAdapter适配Logback
替换Logback默认的ThreadLocal实现,改成读取ScopedValue中的MDC上下文:
import ch.qos.logback.classic.spi.MDCAdapter; import ch.qos.logback.core.spi.ContextAwareBase; import java.util.Map; import java.util.Set; public class ScopedMDCAdapter extends ContextAwareBase implements MDCAdapter { // 复用之前定义的ScopedValue private static final ScopedValue<Map<String, String>> MDC_CONTEXT = ResponseHandler.MDC_CONTEXT; @Override public void put(String key, String val) { Map<String, String> context = MDC_CONTEXT.get(); if (context != null) { context.put(key, val); } } @Override public String get(String key) { Map<String, String> context = MDC_CONTEXT.get(); return context != null ? context.get(key) : null; } @Override public void remove(String key) { Map<String, String> context = MDC_CONTEXT.get(); if (context != null) { context.remove(key); } } @Override public void clear() { Map<String, String> context = MDC_CONTEXT.get(); if (context != null) { context.clear(); } } @Override public Map<String, String> getCopyOfContextMap() { Map<String, String> context = MDC_CONTEXT.get(); return context != null ? Map.copyOf(context) : null; } @Override public void setContextMap(Map<String, String> contextMap) { throw new UnsupportedOperationException("ScopedValue上下文不可直接修改,请通过ScopedValue.where绑定新上下文"); } @Override public Set<String> getKeys() { Map<String, String> context = MDC_CONTEXT.get(); return context != null ? context.keySet() : Set.of(); } }
步骤3:配置Logback使用自定义Adapter
在应用启动时通过系统属性指定,或者在logback.xml中配置:
- 代码方式(放在应用启动入口):
System.setProperty("logback.mdcAdapter", "com.yourpackage.ScopedMDCAdapter");
- 配置文件方式(logback.xml):
<configuration> <!-- 指定自定义MDC适配器 --> <mdcAdapter class="com.yourpackage.ScopedMDCAdapter"/> <!-- 其他日志配置... --> </configuration>
注意事项
ScopedValue是不可变的,若需要在fork任务中修改MDC,需通过ScopedValue.where重新绑定新的不可变Map,避免并发问题。- 该方案完全适配结构化并发模型,不会出现MDC上下文泄漏的情况。
内容的提问来源于stack exchange,提问作者adamw
相关产品推荐
相关产品推荐

