0
# Thread Context
1
2
Thread context provides thread-local diagnostic information storage through MDC (Mapped Diagnostic Context) and NDC (Nested Diagnostic Context). This enables context-aware logging across application threads.
3
4
## Mapped Diagnostic Context (MDC)
5
6
### MDC Class
7
8
```java { .api }
9
public final class MDC {
10
// Context management
11
public static void put(String key, String value);
12
public static Object get(String key);
13
public static String get(String key, String defaultValue);
14
public static void remove(String key);
15
public static void clear();
16
17
// Context retrieval
18
public static Hashtable<String, Object> getContext();
19
public static Map<String, String> getCopyOfContextMap();
20
public static void setContextMap(Map<String, String> contextMap);
21
22
// Utility methods
23
public static Set<String> getKeys();
24
public static boolean isEmpty();
25
}
26
```
27
28
**Parameters:**
29
- `key` - String key for the context value
30
- `value` - String value to store
31
- `defaultValue` - String default value if key not found
32
- `contextMap` - Map of context key-value pairs
33
34
**Returns:**
35
- `Object` or `String` value for the specified key
36
- `Hashtable<String, Object>` current context
37
- `Map<String, String>` copy of context map
38
- `Set<String>` set of all keys
39
- `boolean` indicating if context is empty
40
41
## Nested Diagnostic Context (NDC)
42
43
### NDC Class
44
45
```java { .api }
46
public final class NDC {
47
// Stack operations
48
public static void push(String message);
49
public static String pop();
50
public static String peek();
51
public static void clear();
52
public static void remove();
53
54
// Stack information
55
public static int getDepth();
56
public static Stack cloneStack();
57
public static void inherit(Stack stack);
58
59
// Formatting
60
public static String get();
61
}
62
```
63
64
**Parameters:**
65
- `message` - String message to push onto the stack
66
- `stack` - Stack to inherit from another thread
67
68
**Returns:**
69
- `String` popped or peeked message
70
- `int` current stack depth
71
- `Stack` cloned copy of current stack
72
- `String` formatted NDC string
73
74
## MDC Usage Patterns
75
76
### Basic MDC Operations
77
```java
78
import org.apache.log4j.MDC;
79
import org.apache.log4j.Logger;
80
81
public class MDCExample {
82
private static final Logger logger = Logger.getLogger(MDCExample.class);
83
84
public void processUserRequest(String userId, String sessionId) {
85
// Set context information
86
MDC.put("userId", userId);
87
MDC.put("sessionId", sessionId);
88
MDC.put("operation", "processRequest");
89
90
try {
91
logger.info("Starting user request processing");
92
93
// Business logic
94
processBusinessLogic();
95
96
logger.info("User request completed successfully");
97
} catch (Exception e) {
98
logger.error("User request failed", e);
99
} finally {
100
// Clean up context
101
MDC.clear();
102
}
103
}
104
105
private void processBusinessLogic() {
106
// This method can access MDC context
107
String userId = (String) MDC.get("userId");
108
logger.debug("Processing business logic for user: " + userId);
109
110
// Add more context
111
MDC.put("step", "validation");
112
validateUserData();
113
114
MDC.put("step", "processing");
115
performProcessing();
116
117
MDC.remove("step");
118
}
119
120
private void validateUserData() {
121
logger.debug("Validating user data");
122
}
123
124
private void performProcessing() {
125
logger.debug("Performing main processing");
126
}
127
}
128
```
129
130
### Web Application MDC Filter
131
```java
132
import org.apache.log4j.MDC;
133
import javax.servlet.*;
134
import javax.servlet.http.HttpServletRequest;
135
import java.io.IOException;
136
import java.util.UUID;
137
138
public class MDCFilter implements Filter {
139
140
@Override
141
public void doFilter(ServletRequest request, ServletResponse response,
142
FilterChain chain) throws IOException, ServletException {
143
144
HttpServletRequest httpRequest = (HttpServletRequest) request;
145
146
try {
147
// Set request-specific context
148
MDC.put("requestId", UUID.randomUUID().toString());
149
MDC.put("remoteAddr", request.getRemoteAddr());
150
MDC.put("requestURI", httpRequest.getRequestURI());
151
MDC.put("method", httpRequest.getMethod());
152
153
// Add user context if available
154
String userId = httpRequest.getRemoteUser();
155
if (userId != null) {
156
MDC.put("userId", userId);
157
}
158
159
// Continue filter chain
160
chain.doFilter(request, response);
161
162
} finally {
163
// Always clean up MDC
164
MDC.clear();
165
}
166
}
167
168
@Override
169
public void init(FilterConfig filterConfig) throws ServletException {
170
// Filter initialization
171
}
172
173
@Override
174
public void destroy() {
175
// Filter cleanup
176
}
177
}
178
```
179
180
### MDC Context Map Operations
181
```java
182
import org.apache.log4j.MDC;
183
import java.util.Map;
184
import java.util.HashMap;
185
186
public class MDCContextExample {
187
188
public void demonstrateContextOperations() {
189
// Set individual values
190
MDC.put("user", "john");
191
MDC.put("session", "abc123");
192
MDC.put("operation", "login");
193
194
// Get context copy
195
Map<String, String> contextCopy = MDC.getCopyOfContextMap();
196
System.out.println("Current context: " + contextCopy);
197
198
// Check if context is empty
199
boolean isEmpty = MDC.isEmpty();
200
System.out.println("Context empty: " + isEmpty);
201
202
// Get all keys
203
Set<String> keys = MDC.getKeys();
204
System.out.println("Context keys: " + keys);
205
206
// Create new context map
207
Map<String, String> newContext = new HashMap<>();
208
newContext.put("service", "authentication");
209
newContext.put("version", "1.0");
210
211
// Replace entire context
212
MDC.setContextMap(newContext);
213
214
// Verify change
215
System.out.println("New context: " + MDC.getCopyOfContextMap());
216
217
// Clear all
218
MDC.clear();
219
}
220
}
221
```
222
223
## NDC Usage Patterns
224
225
### Basic NDC Operations
226
```java
227
import org.apache.log4j.NDC;
228
import org.apache.log4j.Logger;
229
230
public class NDCExample {
231
private static final Logger logger = Logger.getLogger(NDCExample.class);
232
233
public void processOrder(String orderId) {
234
NDC.push("OrderService");
235
NDC.push("orderId=" + orderId);
236
237
try {
238
logger.info("Starting order processing");
239
240
validateOrder();
241
processPayment();
242
fulfillOrder();
243
244
logger.info("Order processing completed");
245
} catch (Exception e) {
246
logger.error("Order processing failed", e);
247
} finally {
248
// Pop all contexts for this operation
249
NDC.pop(); // Remove orderId
250
NDC.pop(); // Remove OrderService
251
}
252
}
253
254
private void validateOrder() {
255
NDC.push("validation");
256
try {
257
logger.debug("Validating order details");
258
// Validation logic
259
} finally {
260
NDC.pop();
261
}
262
}
263
264
private void processPayment() {
265
NDC.push("payment");
266
try {
267
logger.debug("Processing payment");
268
// Payment logic
269
} finally {
270
NDC.pop();
271
}
272
}
273
274
private void fulfillOrder() {
275
NDC.push("fulfillment");
276
try {
277
logger.debug("Fulfilling order");
278
// Fulfillment logic
279
} finally {
280
NDC.pop();
281
}
282
}
283
}
284
```
285
286
### NDC Stack Management
287
```java
288
import org.apache.log4j.NDC;
289
import org.apache.log4j.Logger;
290
import java.util.Stack;
291
292
public class NDCStackExample {
293
private static final Logger logger = Logger.getLogger(NDCStackExample.class);
294
295
public void demonstrateStackOperations() {
296
// Build up context stack
297
NDC.push("Application");
298
NDC.push("UserService");
299
NDC.push("createUser");
300
301
// Check stack depth
302
int depth = NDC.getDepth();
303
logger.info("Current NDC depth: " + depth);
304
305
// Peek at top without removing
306
String top = NDC.peek();
307
logger.info("Top of stack: " + top);
308
309
// Get full NDC string
310
String fullContext = NDC.get();
311
logger.info("Full NDC context: " + fullContext);
312
313
// Clone stack for another thread
314
Stack<String> stackCopy = NDC.cloneStack();
315
316
// Pop one level
317
String popped = NDC.pop();
318
logger.info("Popped: " + popped);
319
logger.info("Remaining context: " + NDC.get());
320
321
// Clear all
322
NDC.clear();
323
logger.info("After clear, depth: " + NDC.getDepth());
324
325
// Restore from clone
326
NDC.inherit(stackCopy);
327
logger.info("After inherit: " + NDC.get());
328
329
// Final cleanup
330
NDC.remove();
331
}
332
}
333
```
334
335
### Thread Context Inheritance
336
```java
337
import org.apache.log4j.MDC;
338
import org.apache.log4j.NDC;
339
import org.apache.log4j.Logger;
340
import java.util.Map;
341
import java.util.Stack;
342
import java.util.concurrent.ExecutorService;
343
import java.util.concurrent.Executors;
344
345
public class ThreadContextInheritance {
346
private static final Logger logger = Logger.getLogger(ThreadContextInheritance.class);
347
348
public void processWithThreads() {
349
// Set up context in main thread
350
MDC.put("mainThread", "true");
351
MDC.put("operation", "multiThreadProcess");
352
NDC.push("MainProcessor");
353
354
// Capture context for inheritance
355
Map<String, String> mdcContext = MDC.getCopyOfContextMap();
356
Stack ndcStack = NDC.cloneStack();
357
358
ExecutorService executor = Executors.newFixedThreadPool(3);
359
360
for (int i = 0; i < 3; i++) {
361
final int taskId = i;
362
executor.submit(() -> {
363
try {
364
// Inherit context from parent thread
365
MDC.setContextMap(mdcContext);
366
NDC.inherit(ndcStack);
367
368
// Add thread-specific context
369
MDC.put("threadId", String.valueOf(taskId));
370
NDC.push("Worker-" + taskId);
371
372
logger.info("Processing task in worker thread");
373
374
// Simulate work
375
Thread.sleep(1000);
376
377
logger.info("Task completed in worker thread");
378
379
} catch (InterruptedException e) {
380
logger.warn("Thread interrupted", e);
381
} finally {
382
// Clean up thread context
383
MDC.clear();
384
NDC.remove();
385
}
386
});
387
}
388
389
executor.shutdown();
390
391
// Clean up main thread context
392
MDC.clear();
393
NDC.clear();
394
}
395
}
396
```
397
398
## Pattern Layout Integration
399
400
### Using MDC in Pattern Layouts
401
```java
402
import org.apache.log4j.*;
403
404
public class ContextPatternExample {
405
406
public void setupContextLogging() {
407
// Pattern that includes MDC values
408
PatternLayout mdcPattern = new PatternLayout(
409
"%d{ISO8601} [%t] %-5p %c - [%X{userId}:%X{sessionId}] %m%n"
410
);
411
412
// Pattern that includes specific MDC keys
413
PatternLayout specificMDC = new PatternLayout(
414
"%d %-5p %c - User:%X{userId} Session:%X{sessionId} - %m%n"
415
);
416
417
// Pattern that includes NDC
418
PatternLayout ndcPattern = new PatternLayout(
419
"%d %-5p %c %x - %m%n"
420
);
421
422
// Pattern with both MDC and NDC
423
PatternLayout combinedPattern = new PatternLayout(
424
"%d [%t] %-5p %c - %X{userId} %x - %m%n"
425
);
426
427
// Create appenders
428
ConsoleAppender mdcAppender = new ConsoleAppender(mdcPattern);
429
ConsoleAppender ndcAppender = new ConsoleAppender(ndcPattern);
430
431
// Configure loggers
432
Logger mdcLogger = Logger.getLogger("mdc-logger");
433
mdcLogger.addAppender(mdcAppender);
434
435
Logger ndcLogger = Logger.getLogger("ndc-logger");
436
ndcLogger.addAppender(ndcAppender);
437
}
438
}
439
```
440
441
## Best Practices
442
443
### Context Cleanup Patterns
444
```java
445
import org.apache.log4j.MDC;
446
import org.apache.log4j.NDC;
447
import org.apache.log4j.Logger;
448
449
public class ContextCleanupExample {
450
private static final Logger logger = Logger.getLogger(ContextCleanupExample.class);
451
452
// Using try-with-resources pattern for MDC
453
public static class MDCCloseable implements AutoCloseable {
454
public MDCCloseable(String key, String value) {
455
MDC.put(key, value);
456
}
457
458
@Override
459
public void close() {
460
MDC.clear();
461
}
462
}
463
464
// Using try-with-resources pattern for NDC
465
public static class NDCCloseable implements AutoCloseable {
466
public NDCCloseable(String context) {
467
NDC.push(context);
468
}
469
470
@Override
471
public void close() {
472
NDC.pop();
473
}
474
}
475
476
public void processWithAutoCleanup(String userId, String operation) {
477
// MDC with automatic cleanup
478
try (MDCCloseable mdcContext = new MDCCloseable("userId", userId)) {
479
MDC.put("operation", operation);
480
481
// NDC with automatic cleanup
482
try (NDCCloseable ndcContext = new NDCCloseable("ProcessorService")) {
483
484
logger.info("Starting processing with auto-cleanup");
485
486
// Business logic here
487
performBusinessLogic();
488
489
logger.info("Processing completed");
490
491
} // NDC automatically popped here
492
} // MDC automatically cleared here
493
}
494
495
// Manual cleanup with finally blocks
496
public void processWithManualCleanup(String userId, String operation) {
497
// Set up MDC
498
MDC.put("userId", userId);
499
MDC.put("operation", operation);
500
501
// Set up NDC
502
NDC.push("ManualService");
503
504
try {
505
logger.info("Starting processing with manual cleanup");
506
507
performBusinessLogic();
508
509
logger.info("Processing completed");
510
511
} catch (Exception e) {
512
logger.error("Processing failed", e);
513
throw e;
514
} finally {
515
// Always clean up in reverse order
516
NDC.pop(); // Remove ManualService
517
MDC.clear(); // Clear all MDC values
518
}
519
}
520
521
private void performBusinessLogic() {
522
// This method inherits the context
523
logger.debug("Performing business logic");
524
525
// Can add temporary context
526
MDC.put("step", "validation");
527
NDC.push("validator");
528
529
try {
530
logger.debug("Validating input");
531
// Validation logic
532
} finally {
533
// Clean up temporary context
534
NDC.pop();
535
MDC.remove("step");
536
}
537
}
538
}
539
```
540
541
### Context Performance Considerations
542
```java
543
import org.apache.log4j.MDC;
544
import org.apache.log4j.Logger;
545
546
public class ContextPerformanceExample {
547
private static final Logger logger = Logger.getLogger(ContextPerformanceExample.class);
548
549
public void efficientContextUsage() {
550
// Avoid expensive operations in context values
551
String userId = getCurrentUserId(); // Expensive call
552
MDC.put("userId", userId); // Store result, don't call repeatedly
553
554
// Use conditional logging to avoid context lookups
555
if (logger.isDebugEnabled()) {
556
String contextInfo = buildContextInfo(); // Only when needed
557
logger.debug("Context info: " + contextInfo);
558
}
559
560
// Prefer specific key access over full context maps
561
String user = (String) MDC.get("userId"); // Efficient
562
// Map<String, String> fullContext = MDC.getCopyOfContextMap(); // Less efficient
563
564
// Clean up promptly to avoid memory leaks
565
MDC.clear();
566
}
567
568
private String getCurrentUserId() {
569
// Simulate expensive operation
570
return "user123";
571
}
572
573
private String buildContextInfo() {
574
// Build debug info only when needed
575
return "User: " + MDC.get("userId") + ", Session: " + MDC.get("sessionId");
576
}
577
}
578
```