logbackslf4jmdc

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


After going back to an older project and getting around to update its dependencies I had to realize that logback does not anymore propagate MDCs to children since version 1.1.5: https://github.com/qos-ch/logback/commit/aa7d584ecdb1638bfc4c7223f4a5ff92d5ee6273

This change makes most of the logs nigh useless.

While I can understand the arguments given in the linked issues, I can not understand why this change could not have been made in a more backwards compatible manner (as is generally usual in java..).

Q: What is the now correct way to achieve the same behaviour other than having to subclass everything from Runnables to Threads?


Solution

  • I see no straightforward way to change this back. Two alternatives that come to mind are:

    Way #1: Wrap all Runnables

    Introduce an abstract class that will copy MDC from the original Thread to a new Thread and use it instead of 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();
                }
            };
        }
    }
    

    If some Runnable comes from an external API that you can't change that code, use wrap helper method.

    Drawback: need to analyze and change whole code.

    Way #2: Mess with slf4j internals

    Resurrect the original LogbackMDCAdapter implementation that uses InheritableThreadLocal from before that commit and put it somewhere in your code under some other name. Then somewhere around startup use reflection to override MDC.mdcAdapter property with and instance of that custom implementation. This is obviously a dirty hack but it saves a lot of troubles comparing to #1.

    Note: for performance reasons it makes to inherit your resurrected version from existing LogbackMDCAdapter and just override all the methods with old implementation. See LoggingEvent.java and LogbackMDCAdapter.getPropertyMap internal method for some details.

    Way #3: Mess with logback jar (even stranger alternative)

    This sounds to me as a quite bad plan but for completness here it is.

    Again resurrect the original LogbackMDCAdapter but this time don't rename, compile it and override that .class file inside logback.jar.

    Or resurrect the original LogbackMDCAdapter with renaming, remove .class file for org.slf4j.impl.StaticMDCBinder from logback.jar and add your own class that will return resurrected version of LogbackMDCAdapter either to logback.jar or to your code. MDC seems to be bound to that class by name to create an implementation of MDCAdapter to use.

    Or you can achieve similar result by using custom ClassLoader that would map org.slf4j.impl.StaticMDCBinder to your class instead of the one inside logback.jar. Note: this is probably impossible to achieve inside a Web-container that will add its own custom ClassLoaders.