# How to Use Mapped Diagnostic Context (MDC) in spdlog: A Complete Guide

> Learn how to use Mapped Diagnostic Context MDC in spdlog. Inject contextual data into logs automatically with this comprehensive guide and simplify your debugging.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: how-to-guide
- Published: 2026-07-12

---

**spdlog’s Mapped Diagnostic Context (MDC) provides a thread-local key-value store that automatically injects contextual data into log messages via the `%MDC{key}` pattern placeholder, eliminating the need to pass context variables through every function call.**

The gabime/spdlog library includes a lightweight **Mapped Diagnostic Context (MDC)** facility defined in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h). This feature attaches thread-specific metadata—such as request IDs or user sessions—to your logs without requiring you to modify every logging statement, making it ideal for multi-threaded applications where tracking context across asynchronous operations is critical.

## Understanding spdlog’s MDC Implementation

The MDC implementation in spdlog operates on a **per-thread basis**, storing key-value pairs in thread-local storage. This design ensures that threads never interfere with each other’s context maps and eliminates the need for synchronization locks.

### Core API Functions

According to the source code in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h), the public API exposes four primary operations:

- **`spdlog::set_mdc(key, value)`** – Inserts or updates a key-value pair in the current thread’s MDC map.
- **`spdlog::get_mdc(key)`** – Returns an `optional<std::string>` containing the value if the key exists, or an empty optional otherwise.
- **`spdlog::remove_mdc(key)`** – Removes a specific key from the thread-local context.
- **`spdlog::clear_mdc()`** – Clears all entries from the current thread’s MDC map, which is useful when recycling threads from a pool.

## Configuring MDC Pattern Placeholders

To display MDC values in your log output, you must configure a pattern that includes the `%MDC{key}` placeholder. The pattern formatter, implemented in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h), resolves these placeholders at format time by querying the current thread’s MDC map.

If the specified key does not exist in the MDC map, the placeholder expands to an empty string without raising errors.

## Complete Code Examples

### Single-Threaded Request Tracking

The following example demonstrates setting a request ID at the start of processing and automatically including it in subsequent log entries:

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/mdc.h>

int main()
{
    auto logger = spdlog::stdout_color_mt("example");
    logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%MDC{req_id}] %v");

    // Log without context - placeholder appears empty
    logger->info("Started processing");

    // Attach context to the current thread
    spdlog::set_mdc("req_id", "42a7f9c3");
    logger->info("Handling request");

    // Update context value
    spdlog::set_mdc("req_id", "e9d2a1b0");
    logger->info("Finished request");

    // Remove specific key
    spdlog::remove_mdc("req_id");
    logger->info("After removal");

    // Clean up all context
    spdlog::clear_mdc();
}

```

Output:

```

[2024-01-15 10:30:45.123] [info] [] Started processing
[2024-01-15 10:30:45.124] [info] [42a7f9c3] Handling request
[2024-01-15 10:30:45.125] [info] [e9d2a1b0] Finished request
[2024-01-15 10:30:45.126] [info] [] After removal

```

### Multi-Threaded Server Implementation

Because MDC is thread-local, each thread maintains its own independent context map. This example shows how to track client IDs in a multi-threaded server:

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/mdc.h>
#include <thread>

void handle_client(int client_id)
{
    // Set thread-specific context
    spdlog::set_mdc("client_id", std::to_string(client_id));
    
    auto logger = spdlog::get("server");
    logger->info("Client connected");
    
    // Perform work...
    logger->info("Processing data");
    
    logger->info("Client disconnected");
    
    // Clean up before thread exit
    spdlog::clear_mdc();
}

int main()
{
    auto logger = spdlog::stdout_color_mt("server");
    logger->set_pattern("[%H:%M:%S] [client=%MDC{client_id}] %v");

    std::thread t1(handle_client, 1);
    std::thread t2(handle_client, 2);
    
    t1.join();
    t2.join();
}

```

Each thread prints its assigned `client_id` without lock contention or cross-thread pollution.

## Key Implementation Files

The MDC functionality spans three critical files in the gabime/spdlog repository:

- **[`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h)** – Contains the thread-local storage implementation and the public API functions (`set_mdc`, `get_mdc`, `remove_mdc`, `clear_mdc`).
- **[`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)** – Implements the `%MDC{key}` placeholder handler that queries the MDC map during message formatting.
- **[`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h)** – Provides the `set_pattern()` method used to configure MDC-aware output formats.

## Summary

- **spdlog MDC** stores thread-local key-value pairs accessible throughout the lifetime of a thread.
- Use **`spdlog::set_mdc(key, value)`** to attach context and **`spdlog::clear_mdc()`** to clean up before thread termination.
- Insert the **`%MDC{key}`** placeholder in your log pattern to automatically inject context values into output.
- The implementation resides in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h) and requires no manual synchronization due to thread-local storage.

## Frequently Asked Questions

### Is spdlog MDC thread-safe?

Yes. The MDC implementation uses thread-local storage, meaning each thread maintains its own isolated map. No mutex locks or synchronization primitives are required when calling `set_mdc` or `get_mdc`, as threads cannot access each other’s context data.

### What happens if an MDC key is not found in the pattern?

If the `%MDC{key}` placeholder references a key that does not exist in the current thread’s MDC map, the formatter expands it to an empty string. The logging operation continues normally without throwing exceptions or emitting error messages.

### How do I clear MDC values to prevent memory leaks?

Call `spdlog::clear_mdc()` at the end of your thread’s execution or before returning a thread to a pool. This removes all key-value pairs from the thread-local map. For granular cleanup, use `spdlog::remove_mdc(key)` to delete specific entries without affecting other context data.

### Can I use MDC with asynchronous loggers?

Yes. Because MDC values are captured during the log call (when the message is formatted), the context is preserved even if the actual write to the sink occurs asynchronously on a different thread. The pattern formatter resolves the `%MDC{key}` placeholder using the caller thread’s context before the log message is queued for the backend.