0
# MDC (Mapped Diagnostic Context)
1
2
MDC (Mapped Diagnostic Context) provides a thread-local storage mechanism for associating contextual information with log statements. This enables automatic inclusion of contextual data in log messages without explicitly passing it to each logging call. SLF4J 2.0 extends MDC with stack-based operations for nested contexts.
3
4
## Capabilities
5
6
### Basic MDC Operations
7
8
Core key-value storage operations for thread-local diagnostic context.
9
10
```java { .api }
11
/**
12
* This class hides and serves as a substitute for the underlying logging system's MDC implementation
13
*/
14
public class MDC {
15
/**
16
* Put a diagnostic context value as identified with the key parameter into the current thread's diagnostic context map
17
* @param key non-null key
18
* @param val value to put in the map
19
* @throws IllegalArgumentException in case the "key" parameter is null
20
*/
21
public static void put(String key, String val) throws IllegalArgumentException;
22
23
/**
24
* Get the diagnostic context identified by the key parameter
25
* @param key a key
26
* @return the string value identified by the key parameter
27
* @throws IllegalArgumentException in case the "key" parameter is null
28
*/
29
public static String get(String key) throws IllegalArgumentException;
30
31
/**
32
* Remove the diagnostic context identified by the key parameter
33
* @param key a key
34
* @throws IllegalArgumentException in case the "key" parameter is null
35
*/
36
public static void remove(String key) throws IllegalArgumentException;
37
38
/**
39
* Clear all entries in the MDC of the underlying implementation
40
*/
41
public static void clear();
42
}
43
```
44
45
### Context Map Operations
46
47
Bulk operations for managing the entire diagnostic context.
48
49
```java { .api }
50
/**
51
* Return a copy of the current thread's context map, with keys and values of type String
52
* @return A copy of the current thread's context map. May be null.
53
*/
54
public static Map<String, String> getCopyOfContextMap();
55
56
/**
57
* Set the current thread's context map by first clearing any existing map and then copying the map passed as parameter
58
* @param contextMap must contain only keys and values of type String
59
*/
60
public static void setContextMap(Map<String, String> contextMap);
61
```
62
63
### Auto-Cleanup Support
64
65
Automatic cleanup using try-with-resources pattern.
66
67
```java { .api }
68
/**
69
* Put a diagnostic context value and return a Closeable that removes the key when closed
70
* @param key non-null key
71
* @param val value to put in the map
72
* @return a Closeable who can remove key when close is called
73
* @throws IllegalArgumentException in case the "key" parameter is null
74
*/
75
public static MDCCloseable putCloseable(String key, String val) throws IllegalArgumentException;
76
77
/**
78
* An adapter to remove the key when done
79
*/
80
public static class MDCCloseable implements Closeable {
81
/**
82
* Remove the MDC key when closed
83
*/
84
public void close();
85
}
86
```
87
88
### Stack Operations (SLF4J 2.0)
89
90
Stack-based operations for nested diagnostic contexts.
91
92
```java { .api }
93
/**
94
* Push a value into the deque(stack) referenced by 'key'
95
* @param key identifies the appropriate stack
96
* @param value the value to push into the stack
97
*/
98
public static void pushByKey(String key, String value);
99
100
/**
101
* Pop the stack referenced by 'key' and return the value possibly null
102
* @param key identifies the deque(stack)
103
* @return the value just popped. May be null
104
*/
105
public static String popByKey(String key);
106
107
/**
108
* Returns a copy of the deque(stack) referenced by 'key'. May be null.
109
* @param key identifies the stack
110
* @return copy of stack referenced by 'key'. May be null.
111
*/
112
public Deque<String> getCopyOfDequeByKey(String key);
113
114
/**
115
* Clear the deque(stack) referenced by 'key'
116
* @param key identifies the stack
117
*/
118
public static void clearDequeByKey(String key);
119
```
120
121
### MDC Adapter Interface
122
123
Interface for underlying MDC implementations.
124
125
```java { .api }
126
/**
127
* This interface abstracts the service offered by various MDC implementations
128
*/
129
public interface MDCAdapter {
130
/**
131
* Put a context value as identified by key into the current thread's context map
132
* @param key the key
133
* @param val the value
134
*/
135
public void put(String key, String val);
136
137
/**
138
* Get the context identified by the key parameter
139
* @param key the key
140
* @return the string value identified by the key
141
*/
142
public String get(String key);
143
144
/**
145
* Remove the the context identified by the key parameter
146
* @param key the key to remove
147
*/
148
public void remove(String key);
149
150
/**
151
* Clear all entries in the MDC
152
*/
153
public void clear();
154
155
/**
156
* Return a copy of the current thread's context map
157
* @return A copy of the current thread's context map. May be null.
158
*/
159
public Map<String, String> getCopyOfContextMap();
160
161
/**
162
* Set the current thread's context map by first clearing any existing map and then copying the map
163
* @param contextMap must contain only keys and values of type String
164
*/
165
public void setContextMap(Map<String, String> contextMap);
166
167
/**
168
* Push a value into the deque(stack) referenced by 'key'
169
* @param key identifies the appropriate stack
170
* @param value the value to push into the stack
171
*/
172
public void pushByKey(String key, String value);
173
174
/**
175
* Pop the stack referenced by 'key' and return the value possibly null
176
* @param key identifies the deque(stack)
177
* @return the value just popped. May be null
178
*/
179
public String popByKey(String key);
180
181
/**
182
* Returns a copy of the deque(stack) referenced by 'key'. May be null.
183
* @param key identifies the stack
184
* @return copy of stack referenced by 'key'. May be null.
185
*/
186
public Deque<String> getCopyOfDequeByKey(String key);
187
188
/**
189
* Clear the deque(stack) referenced by 'key'
190
* @param key identifies the stack
191
*/
192
public void clearDequeByKey(String key);
193
}
194
```
195
196
**Usage Examples:**
197
198
```java
199
import org.slf4j.Logger;
200
import org.slf4j.LoggerFactory;
201
import org.slf4j.MDC;
202
import java.util.Map;
203
204
public class MDCExample {
205
private static final Logger logger = LoggerFactory.getLogger(MDCExample.class);
206
207
public void basicMDCUsage() {
208
// Set context information
209
MDC.put("userId", "user123");
210
MDC.put("sessionId", "session456");
211
MDC.put("requestId", "req789");
212
213
// All subsequent log messages will include this context
214
logger.info("User operation started");
215
logger.debug("Processing user data");
216
logger.info("User operation completed");
217
218
// Clean up context
219
MDC.clear();
220
}
221
222
public void autoCleanupUsage() {
223
// Automatic cleanup using try-with-resources
224
try (MDC.MDCCloseable mdcCloseable = MDC.putCloseable("transactionId", "tx123")) {
225
logger.info("Transaction started");
226
227
// Nested context
228
try (MDC.MDCCloseable orderContext = MDC.putCloseable("orderId", "order456")) {
229
logger.info("Processing order");
230
processOrder();
231
logger.info("Order processed successfully");
232
} // orderId automatically removed
233
234
logger.info("Transaction completed");
235
} // transactionId automatically removed
236
}
237
238
public void webRequestExample(String userId, String sessionId, String requestId) {
239
// Set request context at the beginning of request processing
240
MDC.put("userId", userId);
241
MDC.put("sessionId", sessionId);
242
MDC.put("requestId", requestId);
243
MDC.put("thread", Thread.currentThread().getName());
244
245
try {
246
logger.info("Processing web request");
247
248
// All log messages in the call chain will include the context
249
businessService.processRequest();
250
251
logger.info("Web request completed successfully");
252
} catch (Exception e) {
253
logger.error("Web request failed", e);
254
} finally {
255
// Always clean up MDC at the end of request
256
MDC.clear();
257
}
258
}
259
260
public void contextMapOperations() {
261
// Set multiple values
262
Map<String, String> contextMap = Map.of(
263
"userId", "user123",
264
"department", "engineering",
265
"role", "developer",
266
"location", "office-nyc"
267
);
268
269
MDC.setContextMap(contextMap);
270
logger.info("Context established");
271
272
// Get a copy of current context
273
Map<String, String> currentContext = MDC.getCopyOfContextMap();
274
logger.debug("Current context has {} entries", currentContext.size());
275
276
// Modify context
277
MDC.put("operation", "data-processing");
278
logger.info("Starting data processing");
279
280
MDC.clear();
281
}
282
}
283
```
284
285
### Stack-Based Operations (SLF4J 2.0)
286
287
Stack operations enable nested contexts with automatic cleanup:
288
289
```java
290
public class StackMDCExample {
291
private static final Logger logger = LoggerFactory.getLogger(StackMDCExample.class);
292
293
public void nestedOperations() {
294
// Push operation context onto stack
295
MDC.pushByKey("operation", "main-process");
296
logger.info("Starting main process");
297
298
try {
299
// Nested operation
300
MDC.pushByKey("operation", "sub-process-1");
301
logger.info("Starting sub process 1");
302
303
processData();
304
305
logger.info("Sub process 1 completed");
306
MDC.popByKey("operation"); // Remove sub-process-1
307
308
// Another nested operation
309
MDC.pushByKey("operation", "sub-process-2");
310
logger.info("Starting sub process 2");
311
312
processMoreData();
313
314
logger.info("Sub process 2 completed");
315
MDC.popByKey("operation"); // Remove sub-process-2
316
317
logger.info("Main process completed");
318
} finally {
319
MDC.popByKey("operation"); // Remove main-process
320
}
321
}
322
323
public void traceExecutionPath() {
324
MDC.pushByKey("trace", "serviceA");
325
logger.debug("Entering Service A");
326
327
try {
328
MDC.pushByKey("trace", "serviceB");
329
logger.debug("Calling Service B");
330
331
try {
332
MDC.pushByKey("trace", "serviceC");
333
logger.debug("Calling Service C");
334
335
// Process in Service C
336
logger.info("Processing in Service C");
337
338
logger.debug("Exiting Service C");
339
} finally {
340
MDC.popByKey("trace");
341
}
342
343
logger.debug("Exiting Service B");
344
} finally {
345
MDC.popByKey("trace");
346
}
347
348
logger.debug("Exiting Service A");
349
MDC.popByKey("trace");
350
}
351
352
public void inspectStack() {
353
MDC.pushByKey("context", "level1");
354
MDC.pushByKey("context", "level2");
355
MDC.pushByKey("context", "level3");
356
357
// Get copy of the current stack
358
Deque<String> contextStack = MDC.getCopyOfDequeByKey("context");
359
logger.info("Context stack size: {}", contextStack.size());
360
logger.info("Current context: {}", contextStack.peek());
361
362
// Clean up
363
while (!contextStack.isEmpty()) {
364
String popped = MDC.popByKey("context");
365
logger.debug("Popped context: {}", popped);
366
}
367
}
368
}
369
```
370
371
## Common MDC Patterns
372
373
### Web Request Context
374
375
```java
376
@Component
377
public class RequestContextFilter implements Filter {
378
private static final Logger logger = LoggerFactory.getLogger(RequestContextFilter.class);
379
380
@Override
381
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
382
throws IOException, ServletException {
383
384
HttpServletRequest httpRequest = (HttpServletRequest) request;
385
386
// Set up request context
387
MDC.put("requestId", generateRequestId());
388
MDC.put("method", httpRequest.getMethod());
389
MDC.put("uri", httpRequest.getRequestURI());
390
MDC.put("remoteAddr", httpRequest.getRemoteAddr());
391
MDC.put("userAgent", httpRequest.getHeader("User-Agent"));
392
393
// Extract user info if available
394
String userId = extractUserId(httpRequest);
395
if (userId != null) {
396
MDC.put("userId", userId);
397
}
398
399
try {
400
logger.info("Request started");
401
chain.doFilter(request, response);
402
logger.info("Request completed");
403
} catch (Exception e) {
404
logger.error("Request failed", e);
405
throw e;
406
} finally {
407
// Always clean up MDC
408
MDC.clear();
409
}
410
}
411
}
412
```
413
414
### Asynchronous Processing
415
416
```java
417
@Service
418
public class AsyncProcessingService {
419
private static final Logger logger = LoggerFactory.getLogger(AsyncProcessingService.class);
420
421
@Async
422
public CompletableFuture<String> processAsync(String data) {
423
// Copy context from calling thread
424
Map<String, String> contextMap = MDC.getCopyOfContextMap();
425
426
return CompletableFuture.supplyAsync(() -> {
427
// Set context in async thread
428
if (contextMap != null) {
429
MDC.setContextMap(contextMap);
430
}
431
432
// Add async-specific context
433
MDC.put("thread", Thread.currentThread().getName());
434
MDC.put("asyncTask", "data-processing");
435
436
try {
437
logger.info("Starting async processing");
438
String result = performProcessing(data);
439
logger.info("Async processing completed");
440
return result;
441
} catch (Exception e) {
442
logger.error("Async processing failed", e);
443
throw e;
444
} finally {
445
MDC.clear();
446
}
447
});
448
}
449
}
450
```
451
452
### Database Transaction Context
453
454
```java
455
@Component
456
public class TransactionContextManager {
457
private static final Logger logger = LoggerFactory.getLogger(TransactionContextManager.class);
458
459
@EventListener
460
public void handleTransactionBegin(TransactionBeginEvent event) {
461
String transactionId = event.getTransactionId();
462
MDC.put("transactionId", transactionId);
463
MDC.put("transactionStatus", "ACTIVE");
464
MDC.pushByKey("transactionStack", transactionId);
465
466
logger.info("Transaction started");
467
}
468
469
@EventListener
470
public void handleTransactionCommit(TransactionCommitEvent event) {
471
MDC.put("transactionStatus", "COMMITTED");
472
logger.info("Transaction committed");
473
474
MDC.popByKey("transactionStack");
475
cleanupTransactionContext();
476
}
477
478
@EventListener
479
public void handleTransactionRollback(TransactionRollbackEvent event) {
480
MDC.put("transactionStatus", "ROLLED_BACK");
481
logger.warn("Transaction rolled back: {}", event.getCause().getMessage());
482
483
MDC.popByKey("transactionStack");
484
cleanupTransactionContext();
485
}
486
487
private void cleanupTransactionContext() {
488
MDC.remove("transactionId");
489
MDC.remove("transactionStatus");
490
491
// If no more transactions in stack, clean up completely
492
Deque<String> stack = MDC.getCopyOfDequeByKey("transactionStack");
493
if (stack == null || stack.isEmpty()) {
494
MDC.remove("transactionStack");
495
}
496
}
497
}
498
```
499
500
### Security Context
501
502
```java
503
@Component
504
public class SecurityContextManager {
505
private static final Logger logger = LoggerFactory.getLogger(SecurityContextManager.class);
506
507
public void setSecurityContext(Authentication authentication) {
508
if (authentication != null && authentication.isAuthenticated()) {
509
MDC.put("userId", authentication.getName());
510
MDC.put("userRoles", extractRoles(authentication));
511
MDC.put("authMethod", authentication.getClass().getSimpleName());
512
513
if (authentication.getDetails() instanceof WebAuthenticationDetails) {
514
WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails();
515
MDC.put("remoteAddress", details.getRemoteAddress());
516
MDC.put("sessionId", details.getSessionId());
517
}
518
519
logger.debug("Security context established");
520
} else {
521
MDC.put("userId", "anonymous");
522
MDC.put("userRoles", "none");
523
logger.debug("Anonymous security context established");
524
}
525
}
526
527
public void clearSecurityContext() {
528
MDC.remove("userId");
529
MDC.remove("userRoles");
530
MDC.remove("authMethod");
531
MDC.remove("remoteAddress");
532
MDC.remove("sessionId");
533
534
logger.debug("Security context cleared");
535
}
536
}
537
```
538
539
## Best Practices
540
541
### Context Lifecycle Management
542
543
1. **Set Early**: Establish context as early as possible in the request/operation lifecycle
544
2. **Clean Up**: Always clean up MDC context, preferably in finally blocks or using try-with-resources
545
3. **Thread Boundaries**: Copy context when crossing thread boundaries for async operations
546
4. **Nested Contexts**: Use stack operations for nested operations that need hierarchical context
547
548
### Key Naming Conventions
549
550
```java
551
public class MDCKeys {
552
// Request context
553
public static final String REQUEST_ID = "requestId";
554
public static final String SESSION_ID = "sessionId";
555
public static final String USER_ID = "userId";
556
557
// Business context
558
public static final String TRANSACTION_ID = "transactionId";
559
public static final String ORDER_ID = "orderId";
560
public static final String CUSTOMER_ID = "customerId";
561
562
// Technical context
563
public static final String THREAD_NAME = "thread";
564
public static final String OPERATION = "operation";
565
public static final String COMPONENT = "component";
566
}
567
```
568
569
### Performance Considerations
570
571
1. **Avoid Expensive Operations**: Don't put expensive calculations in MDC values
572
2. **String Values Only**: MDC only supports String values, avoid complex object serialization
573
3. **Memory Management**: Clean up context to prevent memory leaks, especially in long-running threads
574
4. **Conditional Population**: Use conditional logic to avoid populating unused context
575
576
```java
577
// Good: Simple string values
578
MDC.put("userId", user.getId());
579
MDC.put("orderTotal", order.getTotal().toString());
580
581
// Avoid: Expensive operations
582
// MDC.put("userProfile", expensiveUserProfileSerialization(user));
583
584
// Good: Conditional population
585
if (logger.isDebugEnabled()) {
586
MDC.put("debugInfo", generateDebugInformation());
587
}
588
```