# spdlog Overflow Policies: block vs overrun_oldest Explained

> Learn spdlog overflow policies: block pauses threads to ensure all messages are written, while overrun_oldest discards old logs for non-blocking writes. Choose the best for your app.

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

---

**The `block` policy pauses the calling thread until space is available in the async queue, guaranteeing every log message is written, while `overrun_oldest` discards the oldest pending message to make room, ensuring non-blocking operation at the cost of losing stale logs.**

When using asynchronous logging in the [gabime/spdlog](https://github.com/gabime/spdlog) library, log messages are queued in a lock-free MPMC blocking queue before being processed by a background thread. If your application produces logs faster than the worker thread can write them, the queue fills up and the `async_overflow_policy` determines whether the producer blocks or drops messages. Understanding these spdlog overflow policies is essential for tuning logging performance and reliability.

## Understanding spdlog Overflow Policies

The behavior when the async queue fills up is governed by the `async_overflow_policy` enum defined in [[`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/async_logger.h#L21-L27). This setting controls how the thread pool handles incoming log messages when the internal queue is saturated.

### The Three Policy Options

While the primary focus is on **`block`** and **`overrun_oldest`**, spdlog implements three distinct strategies:

- **`block`**: The calling thread waits until the queue has free space.
- **`overrun_oldest`**: The oldest entry in the queue is removed to accommodate the new message.
- **`discard_new`**: The incoming message is silently dropped if the queue is full.

The policy is stored as `overflow_policy_` inside the `async_logger` class and passed to the thread pool during message submission.

## block vs overrun_oldest: Behavioral Differences

Choosing between these two policies involves trading off message durability against latency and throughput.

### Block Policy (Guaranteed Delivery)

When **`block`** is selected, the producer thread invokes `q_.enqueue()` which waits indefinitely for space to become available. According to the implementation in [[`include/spdlog/details/thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool-inl.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/details/thread_pool-inl.h#L81-L89), this ensures that every log message is eventually queued and written to the sink.

Use this policy for critical logging where message loss is unacceptable, such as financial transaction records or audit trails. Note that under heavy load, this may introduce back-pressure and increase latency in the producing thread.

### Overrun Oldest Policy (Non-Blocking Operation)

The **`overrun_oldest`** policy calls `q_.enqueue_nowait()`, which internally checks if the queue is full. When capacity is reached, it removes the front (oldest) element before inserting the new message. This happens in the thread pool implementation without blocking the caller.

This policy suits high-throughput scenarios like real-time telemetry or monitoring systems where recent data is more valuable than stale logs. The system maintains maximum throughput even during log bursts, though older messages may be lost.

## Implementation Details in spdlog Source

The overflow policy flows through three key components in the gabime/spdlog source code.

First, the `async_logger` constructor stores the policy selection:

```cpp
// include/spdlog/async_logger.h
async_logger(..., async_overflow_policy overflow_policy = async_overflow_policy::block);

```

When logging occurs, `async_logger::sink_it_` forwards the message along with the stored policy to the thread pool:

```cpp
// include/spdlog/async_logger-inl.h
pool_ptr->post_log(shared_from_this(), msg, overflow_policy_);

```

Finally, the thread pool executes the policy-specific logic:

```cpp
// include/spdlog/details/thread_pool-inl.h
if (overflow_policy == async_overflow_policy::block) {
    q_.enqueue(std::move(new_msg));               // blocks
} else if (overflow_policy == async_overflow_policy::overrun_oldest) {
    q_.enqueue_nowait(std::move(new_msg));        // drops oldest
} else {
    assert(overflow_policy == async_overflow_policy::discard_new);
    q_.enqueue_if_have_room(std::move(new_msg)); // drops new
}

```

## Configuring Overflow Policies in Your Code

Here are practical implementations for each spdlog overflow policy.

### Default Blocking Configuration

By default, async loggers use the `block` policy. The following example creates a thread pool with an 8192-message queue that will stall the producer if saturated:

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

int main() {
    auto tp = std::make_shared<spdlog::details::thread_pool>(8192, 1);
    
    // Defaults to async_overflow_policy::block
    auto logger = std::make_shared<spdlog::async_logger>(
        "my_logger",
        spdlog::sinks_init_list{std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log")},
        tp);

    logger->info("This message will always be queued, even if the queue is full.");
}

```

### Non-Blocking with Overrun Oldest

To prevent blocking during log bursts, explicitly set the policy to `overrun_oldest`:

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

int main() {
    auto tp = std::make_shared<spdlog::details::thread_pool>(8192, 1);
    
    // Discard oldest entries when queue is full
    auto logger = std::make_shared<spdlog::async_logger>(
        "my_logger",
        spdlog::sinks_init_list{std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log")},
        tp,
        spdlog::async_overflow_policy::overrun_oldest);

    logger->info("Newer messages survive even if the queue is saturated.");
}

```

### Using Factory Aliases

spdlog provides convenience type aliases that pre-configure these policies:

```cpp
using spdlog::async_factory;          // Equivalent to block policy
using spdlog::async_factory_nonblock; // Equivalent to overrun_oldest

// Non-blocking factory usage
auto logger = async_factory_nonblock::create<spdlog::sinks::basic_file_sink_mt>(
    "app.log", /*queue_size=*/8192);

```

## Summary

- The **`block`** policy in spdlog ensures guaranteed log delivery by pausing the producer thread until queue space is available, defined in [`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h).
- The **`overrun_oldest`** policy maintains non-blocking operation by discarding the oldest pending message when the queue reaches capacity.
- Policy selection occurs at logger construction and is stored in the `async_logger` class before being passed to the thread pool's `post_async_msg_` method.
- Use **`block`** for critical audit trails where message loss is unacceptable, and **`overrun_oldest`** for high-throughput telemetry where latency matters more than historical completeness.
- The underlying implementation resides in [`include/spdlog/details/thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool-inl.h), utilizing `q_.enqueue()` for blocking and `q_.enqueue_nowait()` for overrun behavior.

## Frequently Asked Questions

### What happens when the spdlog async queue is full?

When the lock-free MPMC queue reaches capacity, behavior depends on the `async_overflow_policy`. With `block`, the calling thread waits until space frees up. With `overrun_oldest`, the oldest queued message is removed to accommodate the new one. With `discard_new`, the new message is dropped silently.

### How do I choose between block and overrun_oldest?

Choose **`block`** when you require 100% message durability and can tolerate potential latency spikes during log bursts, such as in financial applications. Choose **`overrun_oldest`** when maintaining application throughput is critical and you prefer recent logs over older ones, such as in real-time monitoring systems.

### Is there a performance difference between overflow policies?

Yes. The **`block`** policy may reduce throughput under heavy load due to context switches and waiting time in producer threads. The **`overrun_oldest`** policy maintains maximum throughput since `q_.enqueue_nowait()` never blocks, though it incurs the minor overhead of checking queue capacity and potentially calling `pop_front` to remove old entries.

### Can I change the overflow policy at runtime?

No. The overflow policy is set during `async_logger` construction via the `overflow_policy_` member variable and cannot be modified afterward. To use different policies for different logging needs, you must create separate logger instances with different configurations.