# How to Use spdlog Mapped Diagnostic Context (MDC) for Thread-Local Logging

> Learn to use spdlog Mapped Diagnostic Context (MDC) for thread-local logging. Inject contextual data automatically into log messages without code changes. Enhance your debugging.

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

---

**spdlog provides a thread-local Mapped Diagnostic Context (MDC) that stores key-value pairs accessible via pattern placeholders, allowing automatic injection of contextual data into log messages without modifying every log call.**

The [gabime/spdlog](https://github.com/gabime/spdlog) library includes a lightweight MDC facility implemented in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h). This feature enables developers to attach arbitrary contextual information—such as request IDs or user sessions—to individual threads, automatically appending this data to formatted log output through special pattern specifiers.

## What Is the spdlog Mapped Diagnostic Context?

The **Mapped Diagnostic Context (MDC)** is a thread-local storage mechanism that maintains a map of string keys to string values for each thread independently. Because the storage is thread-local, no synchronization primitives like mutexes are required, ensuring zero lock contention during high-throughput logging operations.

When you set an MDC value using `spdlog::set_mdc()`, it stores the data in the current thread's isolated context. The pattern formatter later retrieves these values via the `%MDC{key}` placeholder during log message construction, substituting the key's value or an empty string if absent.

## Core MDC API Functions

The public API exposed in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h) provides four primary operations for managing thread-local context data.

### Setting and Retrieving Values

Use `spdlog::set_mdc(key, value)` to store a string value associated with a specific key for the current thread. To retrieve a value, call `spdlog::get_mdc(key)`, which returns an `optional<std::string>` containing the value if present, or `nullopt` if the key does not exist in the current thread's context.

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

// Store request context
spdlog::set_mdc("request_id", "550e8400-e29b-41d4-a716-446655440000");

// Retrieve the value
auto req_id = spdlog::get_mdc("request_id");
if (req_id) {
    // Use *req_id to access the string value
}

```

### Removing and Clearing Context

To remove a specific key from the current thread's MDC, use `spdlog::remove_mdc(key)`. For bulk cleanup—typically performed before thread termination to prevent memory leaks—call `spdlog::clear_mdc()` to erase all key-value pairs associated with the current thread.

```cpp
// Remove a specific key
spdlog::remove_mdc("request_id");

// Clear all MDC data for this thread
spdlog::clear_mdc();

```

## Configuring MDC Pattern Placeholders

The pattern formatter implemented in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) recognizes the `%MDC{key}` specifier. When formatting a log message, the formatter queries the current thread's MDC map for the specified key and substitutes the stored value into the output string.

If the key does not exist in the current thread's MDC, the placeholder expands to an empty string rather than throwing an exception or logging an error.

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

```

## Practical Usage Examples

### Basic MDC Workflow

This example demonstrates setting a request ID, logging messages with automatic context injection, and clearing the context when finished.

```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 MDC - placeholder renders empty
    logger->info("Started processing");

    // Set request context
    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");

    // Cleanup all thread-local data
    spdlog::clear_mdc();
}

```

### Multi-Threaded Server Implementation

In server applications, each thread handles distinct client connections. The thread-local nature of MDC ensures that context data never bleeds between threads.

```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("Client connected");
    // ... perform work ...
    logger->info("Client disconnected");
    
    // Optional: clear before thread termination
    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 maintains independent `client_id` values without explicit synchronization, as guaranteed by the thread-local storage implementation in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h).

## Implementation Details and Source Files

The MDC functionality spans three primary header files in the spdlog source tree:

- **[`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 backend.
- **[`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)** — Implements the `%MDC{key}` placeholder resolution logic 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()` interface that configures the formatter to recognize MDC placeholders.

According to the gabime/spdlog source code, the MDC map is stored in a thread-local variable, ensuring that concurrent threads manipulate distinct data structures without atomic operations or locks.

## Summary

- **spdlog MDC** provides thread-local key-value storage for contextual logging data accessible via `%MDC{key}` pattern placeholders.
- The API consists of four functions: `set_mdc()`, `get_mdc()`, `remove_mdc()`, and `clear_mdc()` defined in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h).
- Thread-local storage guarantees thread safety without locks, making MDC suitable for high-concurrency applications.
- Missing keys in the MDC map render as empty strings in the log output rather than causing errors.
- Always call `clear_mdc()` before thread termination to release thread-local resources.

## Frequently Asked Questions

### How does spdlog MDC handle concurrent access?

The spdlog MDC implementation uses thread-local storage, meaning each thread maintains its own independent map of key-value pairs. Because threads never share MDC data structures, no mutexes or atomic operations are required, eliminating lock contention even under high concurrency.

### 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 substitutes an empty string for that placeholder. The log message continues processing normally without exceptions or error messages.

### Can I use multiple MDC keys in the same log pattern?

Yes, you can include multiple `%MDC{key}` specifiers in a single pattern string. For example: `[%MDC{req_id}] [%MDC{user_id}] %v`. Each placeholder resolves independently against the current thread's MDC map.

### Is MDC data automatically cleared when a thread exits?

No, MDC data is not automatically cleared upon thread termination. While thread-local storage typically cleans up when a thread joins, explicitly calling `spdlog::clear_mdc()` before thread exit ensures immediate resource release and prevents potential memory leaks in long-running thread pool scenarios.