In this post I am going to show how to copy MDC data from Web threads to @Async threads using a brand new Spring Framework 4.3 feature: ThreadPoolTaskExecutor#setTaskDecorator() [set-task-decorator].
Notice the third and second last log lines: they have [userId:Duke] just left of the log level. The first line is emitted from a Web thread (a @RestController) and the second line is emitted from an @Async method thread. Essentially MDC data was copied from the Web thread onto the @Async thread (That was the cool part 😏).
Read on to see how that can be achieved. All the code presented here can be found in the example project on GitHub. Consult that to see all the details if necessary.
The example project is based on Spring Boot 2. The logging API used here is SLF4J over Logback (use of Logger, LoggerFactory and MDC).
Notice that it logs RestController in action. Also notice that it has this weird call to the repository: messageRepository.findAll().get(). That's because it executes an asynchronous method, receives a Future, and waits for it until it returns. So a Web thread invoking an @Async method. This is obviously a rather contrived example (I guess you use asynchronous methods for something sane in your projects).
Notice that the method logs Repository in action.
The interesting part is that we extend AsyncConfigurerSupport in order to customize the thread pool.
More precisely: executor.setTaskDecorator(new MdcTaskDecorator()). This is how we enable the custom TaskDecorator.
// Right now: Web thread context !
// Right now: @Async thread context !
The decorate() method takes one Runnable and returns another one.
Here, I basically wrap the original Runnable and maintain the MDC data around a delegation to its run() method.
It is actually quite easy to copy MDC data from a Web thread context onto the asynchronous threads' context.
The technique shown here isn't limited to copying MDC data. You can use it to copy other ThreadLocal data as well. You can also use the TaskDecorator for something completely different. Logging, measure asynchronous method durations, swallowing exceptions, exiting the JVM – whatever makes you happy.
A big thank you to Joris Kuipers (@jkuipers) for making me aware of this new functionality in Spring Framework 4.3. An awesome tip 🤗.