# How spdlog's Registry Manages Loggers and the Default Logger

> Discover how spdlog's registry manages loggers and the default logger. Learn about thread-safe storage and zero-overhead default logger access for optimal performance.

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

---

**spdlog uses a global thread-safe registry singleton to store all named loggers as `std::shared_ptr` instances, while the default logger is initialized automatically at startup and accessed via raw pointers for zero-overhead performance.**

The gabime/spdlog library relies on a centralized registry system to track logger lifecycles and enforce global configuration across your application. Understanding how spdlog's registry manages the default logger is essential for building high-performance C++ applications that require runtime logger replacement or thread-safe sink configurations.

## The Global Registry Architecture

The registry is implemented as a singleton pattern in [`include/spdlog/details/registry.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/registry.h) and [`include/spdlog/details/registry-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/registry-inl.h), serving as the authoritative source for all logger instances within a process.

### Singleton Design and Thread Safety

Access the global registry via `spdlog::details::registry::instance()`. This singleton maintains an internal `std::unordered_map<std::string, std::shared_ptr<logger>> loggers_` that requires mutex protection. All mutating operations—including `register_logger()`, `drop()`, and `set_level()`—acquire a lock on `logger_map_mutex_` to ensure thread-safe access to the storage map.

### Global Configuration Storage

Beyond logger storage, the registry holds global settings that propagate to each registered instance. These include the global formatter, log level, flush level, back-trace settings, and a thread-pool reference. When you create a new logger via `initialize_logger()`, the registry automatically applies these global settings to ensure consistent behavior across your logging hierarchy.

## How spdlog's Default Logger Works

The default logger is a special instance created during registry construction, optimized for immediate use without explicit registration steps.

### Automatic Initialization at Startup

During the registry constructor in [`registry-inl.h`](https://github.com/gabime/spdlog/blob/main/registry-inl.h), spdlog automatically creates a stdout color logger using `ansicolor_stdout_sink_mt` on POSIX systems or `wincolor_stdout_sink_mt` on Windows. This logger receives the empty string name `""` and is stored in both the `loggers_` map and a dedicated `default_logger_` member variable for fast access.

### Fast Raw Pointer Access Pattern

Default API calls like `spdlog::info()` do not perform map lookups. Instead, they use `registry::get_default_raw()`, exposed publicly as `spdlog::default_logger_raw()`. This raw pointer access eliminates `shared_ptr` reference counting overhead, making the default API extremely fast. However, the source code in [`registry.h`](https://github.com/gabime/spdlog/blob/main/registry.h) explicitly warns: you must not call `set_default_logger()` from one thread while another thread calls `spdlog::info()` or other default-API functions.

### Safe Shared Pointer Retrieval

For scenarios requiring thread-safe access to the default logger object itself, use `spdlog::default_logger()` defined in [`include/spdlog/spdlog.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog.h). This returns a `std::shared_ptr<logger>` to the current default instance, incrementing the reference count and ensuring the logger remains valid even if another thread replaces the default.

## Replacing the Default Logger at Runtime

You can swap the default logger using `spdlog::set_default_logger(new_logger)`, which forwards to `registry::set_default_logger()`.

The replacement process follows these steps:

1. **Lock Acquisition**: The registry acquires `logger_map_mutex_` to ensure exclusive access
2. **Map Insertion**: The new logger is inserted into `loggers_` using its name as the key (if not already present)
3. **Pointer Update**: The `default_logger_` member is updated to point to the new instance

After replacement, subsequent calls to `spdlog::info()`, `spdlog::debug()`, and other default-API functions immediately operate on the new logger instance.

```cpp
// Use the automatically initialized console logger
spdlog::info("Hello from default stdout logger");

// Extend the default logger with a file sink
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log", true);
spdlog::default_logger()->sinks().push_back(file_sink);
spdlog::info("Now logs to both console and file");

// Replace with a dedicated file logger
auto new_logger = spdlog::basic_logger_mt("file_logger", "output.log");
spdlog::set_default_logger(new_logger);
// After this point, spdlog::info() writes only to output.log

```

## Thread Safety and Concurrency Considerations

While individual loggers are thread-safe, replacing the default logger requires careful synchronization. The raw pointer access used by the default API (`spdlog::default_logger_raw()`) is not atomic with respect to `set_default_logger()`. If you need to swap loggers at runtime, ensure no other threads are actively logging using the default API, or implement your own synchronization layer around logger replacement.

## Summary

- The **registry** is a thread-safe singleton mapping string names to `std::shared_ptr<logger>` instances, defined in [`registry.h`](https://github.com/gabime/spdlog/blob/main/registry.h) and [`registry-inl.h`](https://github.com/gabime/spdlog/blob/main/registry-inl.h)
- **Default logger** is created automatically during registry construction with a color stdout sink, stored with the empty string key `""`
- **Zero-overhead access** is provided via `default_logger_raw()`, returning a raw pointer used by `spdlog::info()` and related macros
- **Safe access** requires `default_logger()` which returns a `shared_ptr` for reference-counted safety
- **Runtime replacement** uses `set_default_logger()` but requires external synchronization to avoid race conditions with active logging calls
- Global configuration stored in the registry propagates to all loggers via `initialize_logger()`

## Frequently Asked Questions

### Where is the spdlog registry defined?

The registry class is declared in [`include/spdlog/details/registry.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/registry.h) and implemented in [`include/spdlog/details/registry-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/registry-inl.h). You access the singleton instance via `spdlog::details::registry::instance()`, though most applications interact with it through the public API in [`include/spdlog/spdlog.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog.h).

### Is spdlog's default logger thread-safe?

Yes, the default logger is thread-safe because it uses mutex-protected sinks (`ansicolor_stdout_sink_mt` or `wincolor_stdout_sink_mt`). However, the act of replacing the default logger via `set_default_logger()` is not synchronized with the raw pointer reads performed by `spdlog::info()` and similar functions, creating a potential race condition during swaps.

### How do I access the default logger without performance overhead?

Use `spdlog::default_logger_raw()` to obtain a raw pointer to the current default logger. The default API macros (`SPDLOG_INFO`, `spdlog::info()`, etc.) use this internally to avoid `shared_ptr` reference counting overhead during each log call, providing near-zero-cost abstraction for the common logging path.

### What happens to the old default logger when I call set_default_logger?

The registry updates its internal `default_logger_` pointer to the new instance and inserts the new logger into the `loggers_` map using its name. The previous default logger remains alive if other `shared_ptr` instances reference it, or if it remains registered in the map under its original name. The registry does not automatically remove the old logger from `loggers_` unless you explicitly call `spdlog::drop()` with its name.