Logback:在 StructuredTaskScope 内创建的分支中 MDC 的可用性

问题描述 投票:0回答:1

在 Java 21+ 中使用

JEP 453
中的 StructuredTaskScope 并分叉多个任务时,我希望将 MDC 值传播到分叉,以便所有日志正确关联。

扩展 JEP 中的示例,我希望所有三个日志都携带相同的 MDC 值:

Response handle() throws ExecutionException, InterruptedException {
  try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    // TODO: set MDC "somehow"

    Supplier<String>  user  = scope.fork(() -> logger.info("1"));
    Supplier<Integer> order = scope.fork(() -> logger.info("2"));

    scope.join().throwIfFailed();

    logger.info("3");
    return new Response(user.get(), order.get());
  }
}

问题是 MDC 不是继承的,这在虚拟线程之前的 Java 中可能不是什么问题。然而,既然创建新线程很便宜并且受到鼓励,继承 MDC 可能会更有用和更常见。

我最初尝试解决这个问题主要是使用

ScopedValue
(JEP 429)。与
ThreadLocal
不同,此类值由范围的分叉继承,因此它们似乎是携带 MDC 标记的良好候选者。

要实现这一点,我必须从 Logback 的日志记录组件中直接访问

ScopedValue
(这可能吗?),或者操作 MDC。我尝试覆盖
MDCAdapter
,但是失败了(Logback 似乎没有使用例如
MDCAdapter.get
来实际读取 MDC 的值)。

java logback mdc virtual-threads project-loom
1个回答
0
投票

OP 使用

ScopedValaue
来存储和访问由
StructuredTaskScope.fork
创建的子任务的 MDC 上下文的想法,似乎是将 MDC 上下文扩展到子任务的最自然的方式。

此方法是在一个小型 POC Spring Boot 应用程序中实现的此处

MDCContext
的自定义实现聚合了两个MDC上下文,一个是根上下文,在大多数情况下是
ThreadLocal
绑定的。这个根上下文保存在“主”线程中设置的值,就问题中引入的代码片段而言,它对应于行
// TODO: set MDC "somehow"
。另一种是子任务上下文,用于在根
ThreadLocal
绑定的 MDC 上下文不可用时访问和设置子任务线程中的 MDC 上下文值。

一个

ScopedValaue
变量,保存子任务上下文,在此自定义
MDCContext
内部声明,它在构造
StructuredTaskScope
时绑定到此上下文的实例:

ScopedValue.runWhere(SUBTASK_CONTEXT, new SubtaskContext()),  () -> {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

        Supplier<String>  user  = scope.fork(() -> logger.info("1"));
        Supplier<Integer> order = scope.fork(() -> logger.info("2"));

        scope.join().throwIfFailed();

        ...
    }
});

创建

SubtaskContext
实例后,需要将 root context 复制到此 subtask context 中,以使根值可用于子任务线程。

最后,要启用

MDCAdapter
的自定义实现,
SLF4JServiceProvider
的实现会在
getMDCAdapter
方法中返回此适配器的实例。

public class ScopedValueServiceProvider extends ... implements SLF4JServiceProvider {
    
    @Override
    public MDCAdapter getMDCAdapter() {
        // ... return our custom MDC Adapter;
    }
    
}
    

,此实现必须指定为系统属性,例如,作为命令行中的 JVM 参数

-slf4j.provider=...ScopedValueServiceProvider

请注意,虽然问题中没有针对它,但这种设计不仅适用于通过

StructuredTaskScope
进行分叉,还适用于使用
ScopedValue.Carrier.run
方法启动的线程,就像在Propagating context through StructuredTaskScope by ScopedValue中讨论的那样 所以问题。

© www.soinside.com 2019 - 2024. All rights reserved.