How spdlog's Registry Manages Multiple Loggers and Default Logger Behavior
spdlog uses a thread-safe global registry (spdlog::details::registry) to store all loggers in an std::unordered_map and provides a fast-access default logger that powers the convenience free-function API.
The spdlog logging library centralizes logger management through a single registry class that coordinates creation, lookup, and global configuration across your entire application. Understanding how this registry works is essential for building robust, multi-logger systems in C++.
How the Registry Stores Multiple Loggers
At the core of spdlog's architecture is the registry class defined in include/spdlog/details/registry.h. This singleton maintains every created logger in a thread-safe map:
std::unordered_map<std::string, std::shared_ptr<logger>> loggers_;
When you create a logger through functions like spdlog::basic_logger_mt() or spdlog::create_logger(), the implementation ultimately invokes one of two registration methods in include/spdlog/details/registry-inl.h:
register_logger()— Lockslogger_map_mutex_, checks for duplicate names (throwing ifthrow_if_exists_is true), then inserts the loggerregister_or_replace()— Locks the same mutex but unconditionally overwrites any existing entry
The mutex-protected design ensures thread-safe access to the logger collection without requiring external synchronization from user code.
Logger Lookup and Retrieval
The registry provides get(name) for retrieving registered loggers by string key:
auto logger = spdlog::get("my_logger"); // Returns shared_ptr or nullptr
This operation acquires logger_map_mutex_ for the duration of the lookup, returning a std::shared_ptr<logger> that keeps the logger alive even if another thread drops it from the registry.
Default Logger Creation and Behavior
The default logger is a special logger automatically constructed during registry initialization unless SPDLOG_DISABLE_DEFAULT_LOGGER is defined at compile time.
Default Logger Initialization
In registry::registry() (lines 33-45 of registry-inl.h), spdlog:
- Creates a logger with an empty string name
"" - Attaches a color sink for console output
- Stores it in both the logger map and the
default_logger_cache member
// The default logger is accessible via
spdlog::default_logger()->info("Direct access");
// Or through the faster raw pointer path
SPDLOG_INFO("Convenience macro uses default_logger_raw()");
Performance Characteristics
The registry optimizes default logger access through dual storage:
| Accessor | Return Type | Speed | Use Case |
|---|---|---|---|
default_logger() |
std::shared_ptr<logger> |
Fast | Direct access with shared ownership |
default_logger_raw() |
logger* |
Fastest | Free-function API (spdlog::info, etc.) |
The default_logger_raw() method returns a raw pointer without locking, making calls like spdlog::info() extremely low overhead. However, this design carries an important constraint documented in registry.h lines 41-44: do not call set_default_logger() concurrently with default-API functions, as the raw pointer may become invalid mid-operation.
Replacing the Default Logger
You can substitute a custom logger as the default using spdlog::set_default_logger():
auto rotating = std::make_shared<spdlog::logger>(
"", // Empty name preserves default logger convention
std::make_shared<spdlog::sinks::rotating_file_sink_mt>("app.log", 1048576, 3)
);
spdlog::set_default_logger(rotating);
This updates both the default_logger_ cache and the registry entry. The previous default logger is dropped only when its name matches the new logger's name.
Global Configuration Across All Loggers
The registry enables system-wide logger configuration through methods that iterate the entire collection:
// Apply to every registered logger simultaneously
spdlog::set_level(spdlog::level::warn); // Filter level
spdlog::set_formatter(std::move(my_formatter)); // Output formatting
spdlog::enable_backtrace(32); // Debug capture
These operations lock logger_map_mutex_ and apply changes to each logger, including the default logger. This guarantees consistent behavior without requiring manual iteration in user code.
Thread Safety Guarantees
The registry employs two separate mutexes for different concerns:
logger_map_mutex_— Protects theloggers_map,default_logger_cache, and related configuration statetp_mutex_— Protects the thread-pool pointer independently
All mutations to the logger collection and default logger state are serialized through logger_map_mutex_. Read operations like get() hold the lock briefly to copy the shared_ptr. The default_logger_raw() accessor bypasses locking entirely for speed, trading safety for performance in the hot path.
Practical Code Examples
Retrieve or Create a Named Logger
auto my_logger = spdlog::get("network");
if (!my_logger) {
my_logger = spdlog::basic_logger_mt("network", "network.log");
}
my_logger->info("Connection established");
Check and Replace Default Logger Configuration
// Inspect current default sink types
auto default_sink = spdlog::default_logger()->sinks().front();
// Swap to async logging with queue
spdlog::set_default_logger(
spdlog::create_async_logger<spdlog::sinks::basic_file_sink_mt>("", "async.log")
);
Bulk Logger Management
// Remove a specific logger from registry
spdlog::drop("network");
// Clear all loggers (use with caution)
spdlog::shutdown(); // Calls registry::shutdown()
Summary
- The spdlog registry (
spdlog::details::registry) maintains anstd::unordered_mapof named loggers protected bylogger_map_mutex_ - Logger registration happens through
register_logger()orregister_or_replace()inregistry-inl.h - The default logger is auto-created with a color console sink, stored under name
"", and cached indefault_logger_for fast access default_logger_raw()provides lock-free access powering thespdlog::info()free-function API- Global configuration methods iterate all registered loggers while holding the mutex for consistent system-wide settings
Frequently Asked Questions
What happens if I create two loggers with the same name?
By default, register_logger() throws an exception when throw_if_exists_ is true. Use register_or_replace() or spdlog::create_or_replace_logger() to silently overwrite existing entries.
Is spdlog::get() thread-safe?
Yes. The get(name) method locks logger_map_mutex_ during lookup and returns a shared_ptr copy. The returned pointer remains valid after the lock releases due to shared ownership.
Can I disable the default logger entirely?
Define SPDLOG_DISABLE_DEFAULT_LOGGER before including any spdlog headers. This prevents automatic default logger construction, though you must then set a default logger manually before using spdlog::info() and similar functions.
Why is set_default_logger() unsafe to call concurrently with logging?
The free-function API (spdlog::info(), etc.) uses default_logger_raw() which returns a raw pointer without synchronization. Concurrent set_default_logger() may delete the old logger while another thread dereferences that raw pointer, causing undefined behavior.
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 →