如何使用现代 logback 取回 MDC "inheritance"?

How to get back MDC "inheritance" with modern logback?

在回到一个较旧的项目并四处更新其依赖项之后,我不得不意识到自版本 1.1.5 以来,logback 不再将 MDC 传播给子项目:https://github.com/qos-ch/logback/commit/aa7d584ecdb1638bfc4c7223f4a5ff92d5ee6273

此更改使大部分日志几乎无用。

虽然我可以理解链接问题中给出的论点,但我不明白为什么不能以更 向后兼容 的方式(通常是通常的方式)进行此更改在 java..).

Q:现在 正确 实现相同行为的方法是什么,除了必须对从 Runnable 到线程的所有内容进行子类化之外?

我看不出有什么直接的方法可以改回来。想到的两个选择是:

方式#1:包装所有Runnables

引入一个抽象class,它将MDC从原来的Thread复制到一个新的Thread,并用它代替Runnable

public abstract class MdcAwareRunnable implements Runnable
{
    private Map<String, String> originalMdc;

    public MdcAwareRunnable()
    {
        this.originalMdc = MDC.getCopyOfContextMap();
    }

    @Override
    public void run()
    {
        MDC.setContextMap(originalMdc);
        runImpl();
    }

    protected abstract void runImpl();

    /**
     * In case some Runnable comes from external API and we can't change that code we can wrap it anyway.
     */
    public static MdcAwareRunnable wrap(Runnable runnable)
    {
        return new MdcAwareRunnable()
        {
            @Override
            protected void runImpl()
            {
                runnable.run();
            }
        };
    }
}

如果某些 Runnable 来自您无法更改该代码的外部 API,请使用 wrap 辅助方法。

缺点:需要分析和更改整个代码。

方式 #2:乱用 slf4j 内部结构

恢复在该提交之前使用 InheritableThreadLocal 的原始 LogbackMDCAdapter 实现,并以其他名称将其放在代码中的某个位置。然后在启动前后的某个地方使用反射来覆盖 MDC.mdcAdapter 属性 和该自定义实现的实例。这显然是一个肮脏的 hack,但与 #1 相比它省去了很多麻烦。

注意:出于性能原因,它会从现有 LogbackMDCAdapter 继承您的复活版本,并仅覆盖所有具有旧实现的方法。有关详细信息,请参阅 LoggingEvent.java and LogbackMDCAdapter.getPropertyMap 内部方法。

方式#3:搞乱 logback jar(甚至更奇怪的选择)

这对我来说听起来是一个很糟糕的计划,但为了完整起见,它确实如此。

再次恢复原来的 LogbackMDCAdapter 但这次不重命名,编译它并覆盖 logback.jar 中的 .class 文件。

或通过重命名恢复原来的 LogbackMDCAdapter,从 logback.jar 中删除 org.slf4j.impl.StaticMDCBinder 的 .class 文件并添加您自己的 class,这将 return LogbackMDCAdapter 的复活版本到 logback.jar 或你的代码。 MDC 似乎按名称绑定到 class 以创建要使用的 MDCAdapter 的实现。

或者您可以通过使用自定义 ClassLoader 实现类似的结果,它将 org.slf4j.impl.StaticMDCBinder 映射到您的 class 而不是 logback.jar 中的那个。注意:这可能无法在将添加自己的自定义类加载器的 Web 容器内实现。

方式四:误用 TurboFilter

ch.qos.logback.classic.Logger 在将记录事件传递给附加程序之前将其传递给过滤器。

方式五:修改日志编码器/提供者 虽然这意味着日志记录事件没有更新,但日志输出将是。