# How spdlog's Registry Manages Multiple Loggers and Default Logger Behavior

> Discover how spdlog's registry efficiently manages multiple loggers and controls default logger behavior. Learn about its thread-safe global registry and fast-access default logger.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: internals
- Published: 2026-08-06

---

**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`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/registry.h). This singleton maintains every created logger in a thread-safe map:

```cpp
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`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/registry-inl.h):

- **`register_logger()`** — Locks `logger_map_mutex_`, checks for duplicate names (throwing if `throw_if_exists_` is true), then inserts the logger
- **`register_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:

```cpp
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`](https://github.com/gabime/spdlog/blob/main/registry-inl.h)), spdlog:

1. Creates a logger with an empty string name `""`
2. Attaches a color sink for console output
3. Stores it in both the logger map and the `default_logger_` cache member

```cpp
// 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`](https://github.com/gabime/spdlog/blob/main/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()`:

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

```cpp
// 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 the `loggers_` map, `default_logger_` cache, and related configuration state
- **`tp_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

```cpp
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

```cpp
// 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

```cpp
// 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 an `std::unordered_map` of named loggers protected by `logger_map_mutex_`
- **Logger registration** happens through `register_logger()` or `register_or_replace()` in [`registry-inl.h`](https://github.com/gabime/spdlog/blob/main/registry-inl.h)
- The **default logger** is auto-created with a color console sink, stored under name `""`, and cached in `default_logger_` for fast access
- **`default_logger_raw()`** provides lock-free access powering the `spdlog::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.