spdlog MDC Mapped Diagnostic Context Usage: Complete Implementation Guide

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 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.

#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.

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 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:

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, 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:

#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:

#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:

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.
  • Configure loggers with %MDC{key} placeholders in 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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →