# How to Enable Asynchronous Logging with spdlog: A Complete Implementation Guide

> Easily implement asynchronous logging with spdlog. Learn how to initialize a thread pool and create async loggers for faster I/O off the main thread. Boost your application's performance today.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: how-to-guide
- Published: 2026-07-27

---

**To enable asynchronous logging with spdlog, initialize a global thread pool using `spdlog::init_thread_pool(queue_size, n_threads)`, then instantiate an `spdlog::async_logger` that forwards log messages to a lock-free queue serviced by background worker threads rather than performing I/O on the calling thread.**

Asynchronous logging prevents your application's hot path from stalling on slow disk or network operations. In the **gabime/spdlog** library, this is achieved through a dedicated thread pool and MPMC (multiple-producer, multiple-consumer) queue architecture defined in [`include/spdlog/async.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async.h) and implemented in [`src/async.cpp`](https://github.com/gabime/spdlog/blob/main/src/async.cpp). This guide provides the exact implementation details based on the spdlog v1.x source code.

## Understanding spdlog's Asynchronous Architecture

When operating in async mode, the `spdlog::async_logger` class (defined in [`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h)) overrides the standard logging behavior. Instead of immediately formatting and writing to sinks, it constructs a lightweight `log_msg` object and pushes it into a lock-free ring buffer. Background **worker threads** (managed in [`src/async.cpp`](https://github.com/gabime/spdlog/blob/main/src/async.cpp)) drain this queue, handle message formatting, and execute the actual I/O operations.

The core components include:
- **[`include/spdlog/async.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async.h)**: Exposes `init_thread_pool()`, `set_async_overflow_policy()`, and factory functions.
- **[`src/async.cpp`](https://github.com/gabime/spdlog/blob/main/src/async.cpp)**: Implements the thread pool singleton and the `worker_thread_func` loop that processes the queue.
- **[`include/spdlog/details/mpmc_blocking_q.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/mpmc_blocking_q.h)**: Contains the lock-free queue implementation used for inter-thread communication.

## Step 1: Initialize the Global Thread Pool

You must create the thread pool **before** constructing any asynchronous loggers. This is a process-wide operation that launches the background worker threads.

```cpp
#include <spdlog/async.h>

// Initialize with 8KB queue size and 1 background thread
spdlog::init_thread_pool(8192, 1);

```

The first parameter specifies the maximum number of pending messages in the queue. The second parameter defines how many worker threads will service the queue. According to [`src/async.cpp`](https://github.com/gabime/spdlog/blob/main/src/async.cpp), this function instantiates a singleton thread pool that remains active for the process lifetime.

## Step 2: Create an Asynchronous Logger

Once the thread pool exists, create loggers using either direct construction or the factory helper.

### Direct Construction Method

For full control over sinks and policies, construct `spdlog::async_logger` explicitly:

```cpp
#include <spdlog/async_logger.h>
#include <spdlog/sinks/basic_file_sink.h>

auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log", true);
auto async_logger = std::make_shared<spdlog::async_logger>(
    "async_file",                           // logger name
    spdlog::sinks_init_list{file_sink},    // sinks
    spdlog::thread_pool(),                  // thread pool reference
    spdlog::async_overflow_policy::block   // overflow policy
);

spdlog::register_logger(async_logger);

```

### Factory Helper Method

For simplified creation, use the factory function provided in [`async.h`](https://github.com/gabime/spdlog/blob/main/async.h):

```cpp
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto logger = spdlog::create_async_logger("my_async_logger", console_sink);

```

## Step 3: Configure Overflow Policies

When the lock-free queue reaches capacity, spdlog applies an **async overflow policy** to determine how to handle new messages:

- **`spdlog::async_overflow_policy::block`** (default): The calling thread blocks until queue space becomes available, ensuring no message loss.
- **`spdlog::async_overflow_policy::overrun_oldest`**: The oldest queued message is discarded to accommodate the new one, guaranteeing the caller never blocks.

Set the policy globally before creating loggers:

```cpp
spdlog::set_async_overflow_policy(spdlog::async_overflow_policy::overrun_oldest);

```

## Complete Working Examples

### Basic Async Logger with File Sink

This example demonstrates the full lifecycle from thread pool initialization to graceful shutdown:

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/basic_file_sink.h>

int main() {
    // 1. Create thread pool: 1MB queue, 2 background threads
    spdlog::init_thread_pool(1024 * 1024, 2);
    
    // 2. Create file sink
    auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("async_log.txt", true);
    
    // 3. Construct async logger using default thread pool
    auto async_logger = std::make_shared<spdlog::async_logger>(
        "async_file_logger",
        spdlog::sinks_init_list{file_sink},
        spdlog::thread_pool(),
        spdlog::async_overflow_policy::block
    );
    
    // 4. Register for global access via spdlog::get()
    spdlog::register_logger(async_logger);
    
    // 5. Use logger - calls return immediately after enqueuing
    async_logger->info("Application started, version {}", 1.2);
    async_logger->warn("Low memory warning");
    async_logger->error("Failed to open {}", "config.json");
    
    // Ensure all queued messages are written before exit
    async_logger->flush();
}

```

### High-Throughput Non-Blocking Configuration

For scenarios requiring maximum throughput without blocking:

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/basic_file_sink.h>

int main() {
    // Smaller queue with overrun policy for fire-and-forget logging
    spdlog::init_thread_pool(4096, 1);
    spdlog::set_async_overflow_policy(spdlog::async_overflow_policy::overrun_oldest);
    
    auto logger = spdlog::create_async_logger("fast_logger",
        std::make_shared<spdlog::sinks::basic_file_sink_mt>("high_perf.log", true));
    
    // This loop never blocks, even if the consumer thread falls behind
    for (int i = 0; i < 1'000'000; ++i) {
        logger->info("High frequency message {}", i);
    }
}

```

## Key Implementation Files in gabime/spdlog

The asynchronous logging system relies on these specific source files:

- **[`include/spdlog/async.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async.h)**: Declares the public API including `init_thread_pool()` and overflow policy enums.
- **[`src/async.cpp`](https://github.com/gabime/spdlog/blob/main/src/async.cpp)**: Implements the global thread pool, the MPMC queue management, and the `worker_thread_func` that runs in background threads.
- **[`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h)**: Defines the `async_logger` class which inherits from `spdlog::logger` but enqueues messages rather than processing them immediately.
- **[`include/spdlog/details/mpmc_blocking_q.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/mpmc_blocking_q.h)**: Contains the lock-free ring buffer implementation that enables efficient producer-consumer communication without mutex contention.

## Summary

- **Thread pool prerequisite**: Always call `spdlog::init_thread_pool()` before instantiating async loggers, as the pool must exist to service the message queue.
- **Lock-free architecture**: The `async_logger` enqueues `log_msg` objects into an MPMC queue ([`include/spdlog/details/mpmc_blocking_q.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/mpmc_blocking_q.h)), decoupling your application from I/O latency.
- **Overflow handling**: Choose between `block` (reliable delivery) and `overrun_oldest` (maximum performance) via `set_async_overflow_policy()`.
- **API compatibility**: Async loggers support the same sink chains and formatting patterns as synchronous loggers, requiring only the thread pool parameter for construction.
- **Resource management**: Background threads are managed automatically by the singleton in [`src/async.cpp`](https://github.com/gabime/spdlog/blob/main/src/async.cpp) and persist until process termination.

## Frequently Asked Questions

### When should I use asynchronous logging with spdlog?

Use asynchronous logging when your application requires deterministic, low-latency performance and cannot tolerate stalls caused by slow I/O operations such as disk writes, network transmissions, or complex formatting. According to the `gabime/spdlog` source code, async mode ensures that log calls from your hot path perform only a lightweight enqueue operation on the lock-free queue, while background worker threads handle the expensive I/O operations.

### What happens if the async queue fills up?

Behavior depends on the **async overflow policy** configured via `spdlog::set_async_overflow_policy()`. With the default `block` policy, the calling thread pauses until space is available in the queue. With `overrun_oldest`, the oldest pending message is discarded to make room for the new one. The queue size is fixed at initialization by the first parameter to `init_thread_pool()`.

### Can I mix synchronous and asynchronous loggers in the same application?

Yes. You can use both standard `spdlog::logger` instances (which perform I/O immediately on the calling thread) and `spdlog::async_logger` instances simultaneously. Only async loggers require the thread pool initialized via `init_thread_pool()`. This flexibility allows you to use synchronous loggers for critical error paths where you must confirm writes, and async loggers for high-volume debug logging.

### How many threads should I allocate to the async thread pool?

For most applications, **one thread** is sufficient because logging is typically I/O bound rather than CPU bound. However, if you have multiple high-throughput loggers with CPU-intensive custom formatting or numerous slow sinks, increasing the thread count (the second parameter to `init_thread_pool()`) can improve throughput. Each thread runs `spdlog::details::worker_thread_func` and shares the single MPMC queue.