# Lifecycle Considerations When Shutting Down Async spdlog Loggers: A Complete Guide

> Safely shut down async spdlog loggers by calling spdlog::shutdown() before exit. Drain queues, stop flushers, and join threads to prevent lost logs and undefined behavior.

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

---

**Call `spdlog::shutdown()` before program exit to drain message queues, stop the periodic flusher, and join background threads, preventing lost logs and undefined behavior.**

The [gabime/spdlog](https://github.com/gabime/spdlog) library provides high-performance asynchronous logging through background thread pools, but proper cleanup is essential to avoid resource leaks and ensure all messages reach their destinations. Understanding the lifecycle considerations when shutting down async spdlog loggers prevents the "static deinitialization order fiasco" and guarantees that queued messages are flushed before termination.

## Why Async Logger Shutdown Matters

Asynchronous loggers in spdlog operate on a **thread-pool** that pulls messages from a lock-free queue and forwards them to sinks. Because logging work happens on background threads, the library must receive explicit notification to clean up resources before the program terminates. Without proper shutdown, queued messages may remain unwritten, background threads may outlive static objects, and the periodic flusher may attempt to access destroyed resources.

## Core Components in the Async Lifecycle

Three key components govern the shutdown sequence according to the spdlog source code.

### async_logger

The `async_logger` class holds a weak reference to the thread-pool and enqueues log and flush requests. In [`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h), the destructor (or explicit `drop` operation) ensures remaining messages are flushed via `backend_flush_()` before the object is destroyed. This guarantees that any pending log entries are processed before the logger is removed from the registry.

### thread_pool

Defined in [`include/spdlog/details/thread_pool.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool.h) and implemented in [`include/spdlog/details/thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool-inl.h), the thread pool owns the background worker threads that consume `async_msg` objects. When the pool is destroyed, it posts a special **terminate** message (`async_msg_type::terminate`) to each thread via `post_async_msg_()` and then `join()`s the threads. This graceful termination sequence ensures workers finish processing current messages before exiting.

### registry

The global `registry` class 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) stores all loggers and owns the thread-pool instance. The `registry::shutdown()` method first stops the periodic flusher, then calls `drop_all()` to remove every logger, and finally resets the thread-pool (`tp_.reset()`), triggering the pool's destructor and thread joins.

## The Shutdown Sequence Explained

When `spdlog::shutdown()` is invoked from [`include/spdlog/spdlog.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog.h), the following occurs:

1. **Periodic Flusher Stop** – The background thread that periodically calls `flush()` on all loggers is terminated first to prevent access to dying resources.
2. **Logger Removal** – `drop_all()` removes every logger from the registry, triggering destructors that flush remaining messages via `backend_flush_()`.
3. **Thread Pool Termination** – The thread pool receives terminate messages for all worker threads, which finish their current work before joining.
4. **Resource Release** – The thread pool is reset, releasing all associated memory and synchronization primitives.

## Recommended Shutdown Patterns

### Global Shutdown at Program Exit

Use this pattern when your application is terminating and you want to ensure all async loggers are properly drained:

```cpp
#include "spdlog/spdlog.h"
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"

int main() {
    // Create async logger using global thread pool
    auto async_file = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>(
        "async_file", "log.txt");
    
    async_file->info("application started");
    
    // Application work here...
    
    // Explicit shutdown before exit
    spdlog::shutdown();   // Drains queues, stops flusher, joins thread pool
    return 0;
}

```

### Dropping a Single Logger

If you need to remove one logger while keeping others active, call `spdlog::drop()`:

```cpp
auto temp_logger = spdlog::create_async<spdlog::sinks::stdout_color_sink_mt>("temp");
temp_logger->info("Temporary log entry");

// Remove specific logger while leaving others running
spdlog::drop("temp");   // Drains its queue before erasing from registry

```

### Custom Thread Pool Management

When using a custom thread pool, normal shutdown still applies:

```cpp
auto tp = std::make_shared<spdlog::details::thread_pool>(8192, 2); // 2 workers
auto logger = std::make_shared<spdlog::async_logger>(
    "custom",
    spdlog::sinks::stdout_sink_mt::instance(),
    tp,
    spdlog::async_overflow_policy::block);

spdlog::register_logger(logger);
logger->info("Using custom thread pool");

// Shutdown cleans up custom pool too
spdlog::shutdown();

```

## Edge Cases and Pitfalls

**Calling `shutdown()` multiple times** – The function is idempotent; subsequent calls become no-ops after the first successful shutdown.

**Logging after shutdown** – Any attempt to log after `spdlog::shutdown()` will recreate a new thread-pool if an async logger is requested, potentially leading to resource leaks if shutdown is not called again before the next termination.

**Static-duration loggers** – Loggers that live for the entire process duration (such as the default logger) must be explicitly dropped or shut down to avoid the static deinitialization order fiasco, where the thread pool might be destroyed before the logger.

## Summary

- **Always call `spdlog::shutdown()`** before program termination to ensure all async messages are written and background threads are joined.
- **The shutdown sequence** stops the periodic flusher, drops all loggers (which flush their queues), and terminates the thread pool via terminate messages and `join()` calls.
- **Use `spdlog::drop("name")`** to remove individual loggers while keeping the system running for other loggers.
- **Static loggers require explicit cleanup** to prevent undefined behavior during static destruction.
- **Key implementation files**: [`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h), [`include/spdlog/details/thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool-inl.h), and [`include/spdlog/details/registry-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/registry-inl.h) contain the critical lifecycle logic.

## Frequently Asked Questions

### What happens if I don't call spdlog::shutdown()?

If you omit `spdlog::shutdown()`, background threads may continue running while static objects are being destroyed, potentially causing crashes or lost log messages. The thread pool destructor may run after dependent resources are gone, leading to undefined behavior. Additionally, any messages still in the queue when the process terminates will be lost.

### Can I drop a single async logger without shutting down everything?

Yes. Call `spdlog::drop("logger_name")` to remove a specific logger from the registry. According to [`include/spdlog/details/registry-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/registry-inl.h), this forces the logger to drain its queue before erasing the entry, allowing other async loggers to continue operating normally.

### Is spdlog::shutdown() safe to call multiple times?

Yes. The `registry::shutdown()` implementation is idempotent. After the first call resets the thread pool and drops all loggers, subsequent calls simply return without attempting to clean up already-freed resources.

### How do I handle static-duration async loggers?

For loggers with static storage duration (typically global or static instances), explicitly call `spdlog::drop("logger_name")` during your application's cleanup phase, or invoke `spdlog::shutdown()` before `main()` returns. This prevents the static deinitialization order fiasco where the logger might attempt to access the thread pool after it has been destroyed.