How to Use Mapped Diagnostic Context (MDC) in spdlog: A Complete Guide
spdlog’s Mapped Diagnostic Context (MDC) provides a thread-local key-value store that automatically injects contextual data into log messages via the %MDC{key} pattern placeholder, eliminating the need to pass context variables through every function call.
The gabime/spdlog library includes a lightweight Mapped Diagnostic Context (MDC) facility defined in include/spdlog/mdc.h. This feature attaches thread-specific metadata—such as request IDs or user sessions—to your logs without requiring you to modify every logging statement, making it ideal for multi-threaded applications where tracking context across asynchronous operations is critical.
Understanding spdlog’s MDC Implementation
The MDC implementation in spdlog operates on a per-thread basis, storing key-value pairs in thread-local storage. This design ensures that threads never interfere with each other’s context maps and eliminates the need for synchronization locks.
Core API Functions
According to the source code in include/spdlog/mdc.h, the public API exposes four primary operations:
spdlog::set_mdc(key, value)– Inserts or updates a key-value pair in the current thread’s MDC map.spdlog::get_mdc(key)– Returns anoptional<std::string>containing the value if the key exists, or an empty optional otherwise.spdlog::remove_mdc(key)– Removes a specific key from the thread-local context.spdlog::clear_mdc()– Clears all entries from the current thread’s MDC map, which is useful when recycling threads from a pool.
Configuring MDC Pattern Placeholders
To display MDC values in your log output, you must configure a pattern that includes the %MDC{key} placeholder. The pattern formatter, implemented in include/spdlog/pattern_formatter.h, resolves these placeholders at format time by querying the current thread’s MDC map.
If the specified key does not exist in the MDC map, the placeholder expands to an empty string without raising errors.
Complete Code Examples
Single-Threaded Request Tracking
The following example demonstrates setting a request ID at the start of processing and automatically including it in subsequent log entries:
#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 context - placeholder appears empty
logger->info("Started processing");
// Attach context to the current thread
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");
// Clean up all context
spdlog::clear_mdc();
}
Output:
[2024-01-15 10:30:45.123] [info] [] Started processing
[2024-01-15 10:30:45.124] [info] [42a7f9c3] Handling request
[2024-01-15 10:30:45.125] [info] [e9d2a1b0] Finished request
[2024-01-15 10:30:45.126] [info] [] After removal
Multi-Threaded Server Implementation
Because MDC is thread-local, each thread maintains its own independent context map. This example shows how to track client IDs in a multi-threaded server:
#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");
// Perform work...
logger->info("Processing data");
logger->info("Client disconnected");
// Clean up before thread exit
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 prints its assigned client_id without lock contention or cross-thread pollution.
Key Implementation Files
The MDC functionality spans three critical files in the gabime/spdlog repository:
include/spdlog/mdc.h– Contains the thread-local storage implementation and the public API functions (set_mdc,get_mdc,remove_mdc,clear_mdc).include/spdlog/pattern_formatter.h– Implements the%MDC{key}placeholder handler that queries the MDC map during message formatting.include/spdlog/logger.h– Provides theset_pattern()method used to configure MDC-aware output formats.
Summary
- spdlog MDC stores thread-local key-value pairs accessible throughout the lifetime of a thread.
- Use
spdlog::set_mdc(key, value)to attach context andspdlog::clear_mdc()to clean up before thread termination. - Insert the
%MDC{key}placeholder in your log pattern to automatically inject context values into output. - The implementation resides in
include/spdlog/mdc.hand requires no manual synchronization due to thread-local storage.
Frequently Asked Questions
Is spdlog MDC thread-safe?
Yes. The MDC implementation uses thread-local storage, meaning each thread maintains its own isolated map. No mutex locks or synchronization primitives are required when calling set_mdc or get_mdc, as threads cannot access each other’s context data.
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 expands it to an empty string. The logging operation continues normally without throwing exceptions or emitting error messages.
How do I clear MDC values to prevent memory leaks?
Call spdlog::clear_mdc() at the end of your thread’s execution or before returning a thread to a pool. This removes all key-value pairs from the thread-local map. For granular cleanup, use spdlog::remove_mdc(key) to delete specific entries without affecting other context data.
Can I use MDC with asynchronous loggers?
Yes. Because MDC values are captured during the log call (when the message is formatted), the context is preserved even if the actual write to the sink occurs asynchronously on a different thread. The pattern formatter resolves the %MDC{key} placeholder using the caller thread’s context before the log message is queued for the backend.
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 →