or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

basic-logging.mdfluent-logging.mdindex.mdmarkers.mdmdc.mdservice-providers.md

mdc.mddocs/

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

```