spdlog Async Overflow Policies: Block vs Overrun Oldest Explained
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 (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, 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 (lines 81-89) implements the policy-specific behavior in post_async_msg_:
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_.enqueueperforms a blocking push that waits for free space.q_.enqueue_nowaitinternally callspop_fronton the queue when full, discarding the oldest element before inserting the new one.q_.enqueue_if_have_roomchecks capacity and skips insertion if the queue is saturated.
Async Logger Integration
In include/spdlog/async_logger-inl.h, the sink_it_ method forwards messages to the thread pool using the stored policy:
// 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:
#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:
#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:
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
blockwhen 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_oldestwhen 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. block(default) causes the producer thread to wait viaq_.enqueue(), ensuring 100% message delivery at the cost of potential latency.overrun_oldestcallsq_.enqueue_nowait()to drop the oldest entry when full, maintaining non-blocking operation for the producer.- The policy is passed from
async_loggertothread_poolviapost_logand evaluated inpost_async_msg_inthread_pool-inl.h. - Factory aliases
async_factoryandasync_factory_nonblockprovide 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →