Explain Codes LogoExplain Codes Logo

How to use MDC with thread pools?

java
thread-pool
mdc
logging
Alex KataevbyAlex Kataev·Feb 22, 2025
TLDR

Save MDC (Mapped Diagnostic Context) while in Thread Pool land by wrapping tasks in a MdcAwareRunnable or MdcAwareCallable. This helpful wrapper copies the task's MDC before the task runs and clears it afterwards.

public class MdcAwareRunnable implements Runnable { private final Runnable task; private final Map<String, String> mdcContext; public MdcAwareRunnable(Runnable task) { this.task = task; this.mdcContext = MDC.getCopyOfContextMap(); // Grabs current MDC } @Override public void run() { MDC.setContextMap(mdcContext); // Don't leave home without it! try { task.run(); // Dear task, go do your thing } finally { MDC.clear(); // Cleanliness is next to Godliness } } }

Remember to wrap your tasks in this MdcAwareRunnable for a swim in the ExecutorService - it's MDC safe!

Advanced strategies: Because we can always go deeper

When ThreadPoolExecutor becomes your friend

Customize ThreadPoolExecutor by overriding beforeExecute and afterExecute. This makes sure that your task's MDC values are copied over and restored in absolute secrecy. Well, not really, but it's fun to pretend, isn't it?

public class MdcThreadPoolExecutor extends ThreadPoolExecutor { // Constructor and other code here... @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); MDC.setContextMap(getMdcContext(r)); // Shuttle service for MDC context } @Override protected void afterExecute(Runnable r, Throwable t) { try { // You can do your cleanup or logging here. Leave it cleaner than you found it! } finally { MDC.clear(); // Bye bye, old MDC super.afterExecute(r, t); } } private Map<String, String> getMdcContext(Runnable r) { if (r instanceof MdcAwareRunnable) { return ((MdcAwareRunnable) r).mdcContext; // Hand it over, please! } // Empty context for everyone else... Life is cruel, isn't it? return Collections.emptyMap(); } }

Using Spring? Meet the MdcTaskDecorator

If you're using Spring, TaskDecorator can make your ThreadPoolTaskExecutor get along with MDC like two peas in a pod:

public class MdcTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { Map<String, String> contextMap = MDC.getCopyOfContextMap(); // Swiper, no swiping! return () -> { try { MDC.setContextMap(contextMap); // Your new outfit, young Runnable runnable.run(); // Run, Forrest! Run! } finally { MDC.clear(); // Rinse and repeat } }; } }

And the Oscar for the best supporting decorator in a ThreadPoolTaskExecutor goes to... MdcTaskDecorator:

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(new MdcTaskDecorator()); // See you on the red carpet!

Remember, with great MDC comes great responsibility

Your task is to thread carefully! (pun intended) MDC operations should be thread-safe within tasks. Local variables are your friends, while shared state is that friendly-looking stranger offering you unwanted candies.

Getting serious with Monitoring and Reliability

We hate to be the party poopers but evaluate the impact of MDC on your jazzy live systems. MDC operations are like lightweight boxers – punching way above their weight class but with small overhead in high-load scenarios.

But what if it goes wrong? Here are common pitfalls and solutions

Stale MDC values

You don't want old, moldy MDC values, do you? Use MdcAwareRunnable to clear the MDC once task finishes. Now, subsequent tasks have a sparkling clean context.

Hiccups in MDC context propagation

Got nested tasks? Make sure the MDC context is handed down like a cherished family recipe. You might need extra wrappers or tweaks to manage nested contexts.

Overhead concerns

MDC context copying may have some overhead, you don't want your tasks to run like they're wearing lead boots. Strike the balance between rich, detailed logging and smooth, seamless performance.

Shared state within MDC

If multiple tasks are updating the same MDC keys, it's like a battle royale for data! Use proper synchronization to avoid such data bloodbath and keep the peace.

Handling thread pool shutdown like a pro

Even during a shutdown, you need MDC context for logging. Make sure to clear the decks (and MDC) as a part of your shutdown sequence.