What is the Mapped Diagnostic Context (MDC) in spdlog?
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. 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 astd::optional<std::string>. Returnsstd::nulloptif 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. 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.
#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:
#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– Defines the public API (set_mdc,get_mdc,remove_mdc,clear_mdc) and the thread-local storage container.include/spdlog/pattern_formatter.h– Implements the%MDC{key}token resolution logic that queries the MDC during message formatting.include/spdlog/logger.h– Provides theset_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, andclear_mdcfunctions available in thespdlognamespace viainclude/spdlog/mdc.h. - Context values are accessed in patterns using the
%MDC{key}placeholder, resolved at formatting time bypattern_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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →