# spdlog Async Overflow Policies: Block vs Overrun Oldest Explained

> Understand spdlog async overflow policies block vs overrun_oldest. Learn how to handle full logging queues to prevent blocking or losing critical messages.

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

---

**When spdlog's asynchronous logging queue fills up, the `block` policy causes the calling thread to wait until space is available, while `overrun_oldest` immediately discards the oldest pending log entry to accept the new message without blocking.**

The gabime/spdlog library implements high-performance asynchronous logging through a lock-free **MPMC blocking queue** that buffers log messages between producer threads and background worker threads. When this queue reaches capacity, the `async_overflow_policy` enum defined in [`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h) (lines 21-27) determines whether the system prioritizes data integrity or latency.

## Understanding the Async Overflow Policy Enum

spdlog defines three overflow behaviors, though production code typically chooses between the two primary policies.

### The Policy Definition

In [`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h), the enum specifies the available strategies:

- **`block`**: The calling thread **blocks** until the queue has space. The log message is always eventually enqueued.
- **`overrun_oldest`**: The queue **drops the oldest entry** to make room for the new one. No blocking occurs.
- **`discard_new`**: The new message is silently discarded when the queue is full (useful when older logs must be preserved).

The policy is stored as a member variable `overflow_policy_` inside the `async_logger` class and defaults to `block` in the constructor.

### Behavioral Comparison

| Policy | Latency Impact | Data Guarantee | Use Case |
|--------|----------------|----------------|----------|
| **block** | Variable (waits for space) | 100% delivery | Financial transactions, audit logs |
| **overrun_oldest** | Zero (immediate return) | Recent data prioritized | Real-time telemetry, high-frequency monitoring |

## How Overflow Policies Work Internally

The policy selection affects how the **thread pool** interacts with the underlying lock-free queue.

### Thread Pool Dispatch Logic

When a log message is posted, [`include/spdlog/details/thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool-inl.h) (lines 81-89) implements the policy-specific behavior in `post_async_msg_`:

```cpp
if (overflow_policy == async_overflow_policy::block) {
    q_.enqueue(std::move(new_msg));               // blocks until space available
} else if (overflow_policy == async_overflow_policy::overrun_oldest) {
    q_.enqueue_nowait(std::move(new_msg));        // drops oldest if full
} else {
    q_.enqueue_if_have_room(std::move(new_msg));  // drops new if full
}

```

- **`q_.enqueue`** performs a blocking push that waits for free space.
- **`q_.enqueue_nowait`** internally calls `pop_front` on the queue when full, discarding the oldest element before inserting the new one.
- **`q_.enqueue_if_have_room`** checks capacity and skips insertion if the queue is saturated.

### Async Logger Integration

In [`include/spdlog/async_logger-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger-inl.h), the `sink_it_` method forwards messages to the thread pool using the stored policy:

```cpp
// Forward to thread pool with the overflow policy
pool_ptr->post_log(shared_from_this(), msg, overflow_policy_);

```

This ensures that every log call respects the overflow policy configured at logger creation.

## Implementing spdlog Async Overflow Policies

Configure the behavior by passing the `async_overflow_policy` enum to the `async_logger` constructor or using the factory aliases.

### Example: Default Block Policy

By default, async loggers use the `block` policy to guarantee message delivery:

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

int main() {
    // Create thread pool with 8192 message capacity
    auto tp = std::make_shared<spdlog::details::thread_pool>(8192, 1);
    
    // Default overflow_policy is 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);  // Defaults to async_overflow_policy::block

    logger->info("This call blocks if the queue is full, ensuring the message is logged.");
}

```

### Example: Overrun Oldest Configuration

For high-throughput scenarios where latency is critical, explicitly select `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);
    
    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);  // Non-blocking

    logger->info("If the queue is full, the oldest entry is dropped to make room for this.");
}

```

### Using Factory Aliases

spdlog provides convenience aliases that pre-select the policy:

```cpp
using spdlog::async_factory;          // block policy (default)
using spdlog::async_factory_nonblock; // overrun_oldest policy

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

```

## Choosing Between Block and Overrun Oldest

Select the appropriate policy based on your application's constraints:

- **Use `block`** when log completeness is mandatory. This is essential for compliance logging, financial records, or debugging data where every message counts. The trade-off is potential latency spikes in the producer thread when the system is under heavy load.

- **Use `overrun_oldest`** when you need guaranteed low latency and recent data is more valuable than stale logs. This suits real-time telemetry, performance metrics, or monitoring systems where a 30-second-old log is less relevant than the current error state.

## Summary

- **spdlog async overflow policies** control behavior when the MPMC queue reaches capacity, defined in [`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h).
- **`block`** (default) causes the producer thread to wait via `q_.enqueue()`, ensuring 100% message delivery at the cost of potential latency.
- **`overrun_oldest`** calls `q_.enqueue_nowait()` to drop the oldest entry when full, maintaining non-blocking operation for the producer.
- The policy is passed from `async_logger` to `thread_pool` via `post_log` and evaluated in `post_async_msg_` in [`thread_pool-inl.h`](https://github.com/gabime/spdlog/blob/main/thread_pool-inl.h).
- Factory aliases `async_factory` and `async_factory_nonblock` provide convenient configuration shortcuts.

## Frequently Asked Questions

### What is the default spdlog async overflow policy?

The default policy is **`block`**, defined in the `async_logger` constructor in [`include/spdlog/async_logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async_logger.h). If you do not specify an overflow policy when creating an async logger, the producer thread will wait for queue space rather than dropping messages.

### Does overrun_oldest delete the oldest log entry permanently?

Yes. When using `overrun_oldest` and the queue is full, `q_.enqueue_nowait()` internally calls `pop_front` to remove the oldest entry before inserting the new message. That log entry is lost and will not be written to the sink.

### Can I monitor how many messages spdlog drops?

spdlog does not expose a direct counter for dropped messages in the `overrun_oldest` or `discard_new` policies. If you need to track drops, you must implement a custom sink or wrap the queue logic, as the standard MPMC blocking queue implementation does not provide statistics on discarded items.

### What is the difference between overrun_oldest and discard_new?

**`overrun_oldest`** removes the oldest message from the queue to make room for the new one, preserving the most recent log data. **`discard_new`** simply refuses to add the incoming message if the queue is full, preserving all older messages but losing the current one. Choose `overrun_oldest` for real-time systems where recent data matters, and `discard_new` when you must guarantee that once-queued messages are never evicted.