# What is the Mapped Diagnostic Context (MDC) in spdlog?

> Learn about the Mapped Diagnostic Context MDC in spdlog. Automatically inject thread-local key-value data into your logs with the %MDC{key} pattern for better context.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: deep-dive
- Published: 2026-07-20

---

**The Mapped Diagnostic Context (MDC) in spdlog is a thread-local key-value storage system that enables automatic injection of contextual data into log messages via the `%MDC{key}` pattern placeholder.**

The MDC facility in the `gabime/spdlog` repository allows C++ applications to attach arbitrary metadata—such as request IDs, user sessions, or transaction identifiers—to specific threads without modifying every log call. This context automatically propagates to log output when using pattern formatters, solving the common problem of passing contextual data through deep call stacks.

## Understanding the MDC Architecture

The MDC implementation resides in the `spdlog::detail::mdc` namespace and is exposed publicly through [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h). It operates on a **per-thread basis**, meaning each thread maintains its own independent map of key-value pairs. Because the storage is thread-local, no synchronization primitives (mutexes or locks) are required, ensuring zero contention between threads.

When a logger formats a message containing the `%MDC{key}` placeholder, the pattern formatter queries the current thread’s MDC map. If the key exists, its corresponding value is inserted into the output; if absent, the placeholder expands to an empty string.

## Core MDC API Functions

The public API exposes four primary operations for managing thread-local context:

- **`spdlog::set_mdc(key, value)`** – Inserts or updates a key-value pair in the current thread’s MDC map.
- **`spdlog::get_mdc(key)`** – Retrieves the value associated with a key, returning a `std::optional<std::string>`. Returns `std::nullopt` if the key does not exist.
- **`spdlog::remove_mdc(key)`** – Deletes a specific key from the current thread’s context.
- **`spdlog::clear_mdc()`** – Clears all key-value pairs from the current thread’s MDC map, useful for cleaning up when a thread exits or finishes processing a request.

## Pattern Formatter Integration

The connection between MDC data and log output occurs in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h). When you define a pattern using `%MDC{key}`, the formatter dynamically resolves the placeholder at log time by calling the MDC retrieval API. This integration allows you to position context data anywhere in your log format—timestamps, log levels, and MDC values can be interleaved arbitrarily.

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

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

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

    // Set thread-local context
    spdlog::set_mdc("req_id", "42a7f9c3");
    logger->info("Handling request");  // Outputs: [...] [info] [42a7f9c3] Handling request

    // Update context mid-flow
    spdlog::set_mdc("req_id", "e9d2a1b0");
    logger->info("Finished request");  // Outputs: [...] [info] [e9d2a1b0] Finished request

    // Cleanup
    spdlog::remove_mdc("req_id");
    spdlog::clear_mdc();
}

```

## Multi-Threaded Usage Example

Because MDC maps are thread-local, you can safely set different contexts for concurrent threads without cross-contamination. Each thread manages its own isolated scope:

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

void handle_client(int client_id)
{
    // Attach client-specific context to this thread only
    spdlog::set_mdc("client_id", std::to_string(client_id));
    
    auto logger = spdlog::get("server");
    logger->info("Connected");
    // ... business logic ...
    logger->info("Disconnected");
    
    // Optional: clean up before thread exits
    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();
}

```

In this example, thread `t1` prints `client=1` while thread `t2` prints `client=2`, with no manual passing of context variables through function parameters.

## Key Implementation Files

The MDC functionality is distributed across three critical headers in the spdlog codebase:

- **[`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h)** – Defines the public API (`set_mdc`, `get_mdc`, `remove_mdc`, `clear_mdc`) and the thread-local storage container.
- **[`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)** – Implements the `%MDC{key}` token resolution logic that queries the MDC during message formatting.
- **[`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h)** – Provides the `set_pattern()` method that configures how MDC placeholders are rendered in the final log line.

## Summary

- The **Mapped Diagnostic Context** provides thread-local storage for key-value pairs that automatically inject into log messages.
- The API consists of `set_mdc`, `get_mdc`, `remove_mdc`, and `clear_mdc` functions available in the `spdlog` namespace via [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h).
- Context values are accessed in patterns using the `%MDC{key}` placeholder, resolved at formatting time by [`pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/pattern_formatter.h).
- Thread-local implementation ensures **lock-free operation** and complete isolation between threads.
- Missing keys in the MDC map result in empty string substitution rather than errors or exceptions.

## Frequently Asked Questions

### Is spdlog MDC thread-safe?

Yes. The MDC uses thread-local storage, meaning each thread maintains a completely separate map instance. No mutexes or atomic operations are required when reading or writing MDC values, making it safe to use in high-concurrency environments without performance penalties.

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

If the pattern contains `%MDC{key}` but the key has not been set via `set_mdc()`, the placeholder expands to an empty string. The logging operation continues normally without throwing exceptions or logging errors.

### How do I clear MDC values after processing a request?

Use `spdlog::clear_mdc()` to remove all key-value pairs from the current thread’s context, or use `spdlog::remove_mdc("key")` to delete a specific entry. Calling `clear_mdc()` is recommended at the end of request processing to prevent context leakage between unrelated operations on pooled threads.

### Does using MDC impact logging performance?

The performance impact is minimal. MDC lookups occur during pattern formatting and involve an unordered_map lookup on thread-local data. Since no synchronization is required, the overhead consists solely of the hash map retrieval and optional string copy, typically negligible compared to I/O operations.