# spdlog MDC Mapped Diagnostic Context Usage: Complete Implementation Guide

> Master spdlog MDC Mapped Diagnostic Context with this guide. Learn to efficiently inject key-value pairs into logs, automating context for cleaner, more informative messages. Implement today.

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

---

**spdlog provides a thread-local Mapped Diagnostic Context (MDC) facility that stores key-value pairs via `set_mdc()` and injects them into log messages using the `%MDC{key}` pattern placeholder, eliminating the need to pass context manually to every log call.**

The gabime/spdlog library includes a lightweight Mapped Diagnostic Context implementation that enables per-thread diagnostic storage. Understanding spdlog MDC Mapped Diagnostic Context usage allows you to attach request IDs, user identifiers, or transaction metadata to logging threads and automatically include this data in formatted output.

## Core MDC API Functions

The MDC API resides in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h) and operates on a per-thread basis using thread-local storage. Each thread maintains its own independent map, requiring no synchronization between threads.

### Setting Context Values

Use `spdlog::set_mdc(key, value)` to store a string value associated with a specific key for the current thread. This function accepts two `std::string` parameters and updates the thread-local map.

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

// Attach a request ID to the current thread
spdlog::set_mdc("req_id", "42a7f9c3");

```

### Retrieving and Removing Values

Retrieve values using `spdlog::get_mdc(key)`, which returns an `optional<std::string>`. If the key exists, the optional contains the value; otherwise, it is empty.

```cpp
auto req_id = spdlog::get_mdc("req_id");
if (req_id) {
    // Process the retrieved value
}

```

Remove specific keys with `spdlog::remove_mdc(key)` to delete individual entries from the current thread's context.

### Clearing the Context

Call `spdlog::clear_mdc()` to remove all key-value pairs from the current thread's MDC map. This is particularly useful when recycling threads from a pool or at the end of request processing to prevent data leakage between sessions.

## Configuring Log Patterns with %MDC

The pattern formatter in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) implements the `%MDC{key}` placeholder. When processing log messages, the formatter queries the current thread's MDC map for the specified key and substitutes the placeholder with the stored value or an empty string if the key is absent.

Configure your logger to use MDC placeholders in the pattern string:

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

```

## Thread Safety and Implementation Details

The MDC implementation stores data in thread-local storage (`thread_local` variables), ensuring that each thread has an isolated context map. This design eliminates lock contention and makes spdlog MDC Mapped Diagnostic Context usage safe for high-concurrency applications without additional synchronization overhead.

According to the source code in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h), the internal implementation resides in `spdlog::detail::mdc` but exposes convenient wrapper functions in the main `spdlog` namespace.

## Practical Implementation Examples

### Basic Request Tracking

This example demonstrates setting a request ID, logging messages with automatic context injection, and cleaning up afterward:

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

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

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

    // Set request context
    spdlog::set_mdc("req_id", "42a7f9c3");
    logger->info("Handling request");

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

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

    // Clear all context
    spdlog::clear_mdc();
}

```

### Multi-Threaded Server Implementation

In server applications, each client connection typically runs on its own thread. The MDC automatically isolates context between threads:

```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");
    
    // Processing occurs here...
    
    logger->info("Client disconnected");
    
    // Clean up thread context 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();
}

```

## Key Source Files

The MDC functionality spans several header files in the gabime/spdlog repository:

- **[`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h)** — Contains the public API (`set_mdc`, `get_mdc`, `remove_mdc`, `clear_mdc`) and the thread-local storage implementation.
- **[`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)** — Implements the `%MDC{key}` placeholder resolution that queries the MDC map during message formatting.
- **[`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h)** — Defines the logger interface where patterns are configured via `set_pattern()`.

## Summary

- **spdlog MDC Mapped Diagnostic Context usage** enables thread-local storage of diagnostic key-value pairs accessible through pattern placeholders.
- Use `spdlog::set_mdc()`, `spdlog::get_mdc()`, `spdlog::remove_mdc()`, and `spdlog::clear_mdc()` to manage context in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h).
- Configure loggers with `%MDC{key}` placeholders in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h) to automatically inject context into messages.
- The thread-local implementation requires no synchronization and safely isolates data between threads.

## Frequently Asked Questions

### How does spdlog handle missing MDC keys in pattern strings?

When a log pattern contains `%MDC{key}` but the key does not exist in the current thread's MDC map, the pattern formatter substitutes an empty string. This behavior ensures that log formatting continues uninterrupted even if context has not been set or was removed.

### Is spdlog MDC thread-safe for concurrent access?

Yes, the MDC implementation uses thread-local storage (`thread_local`), meaning each thread maintains its own independent map. According to the implementation in [`include/spdlog/mdc.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/mdc.h), no locks are required because threads cannot access each other's MDC data, making it safe for high-concurrency scenarios.

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

Yes, you can include multiple `%MDC{key}` placeholders with different keys 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, allowing you to display multiple context values simultaneously.

### When should I call `spdlog::clear_mdc()`?

Call `spdlog::clear_mdc()` at the end of a request lifecycle, when recycling threads from a pool, or before terminating threads. This prevents context leakage where values from one session or request inadvertently appear in subsequent operations on the same thread, ensuring clean separation between logging contexts.