# Synchronous vs Asynchronous Loggers in spdlog: Key Differences Explained

> Understand the key differences between synchronous and asynchronous loggers in spdlog. Learn how spdlog optimizes logging performance for your applications.

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

---

**Synchronous loggers process log messages directly in the caller thread while asynchronous loggers enqueue messages to a lock-free queue processed by a background thread pool, trading immediate consistency for higher throughput.**

The spdlog library provides two distinct logger implementations that differ fundamentally in how they handle message dispatch and I/O operations. Understanding the difference between synchronous and asynchronous loggers in spdlog is essential for optimizing application performance and ensuring reliable logging under varying load conditions. This article examines the architectural distinctions, performance characteristics, and configuration options based on the source code in the `gabime/spdlog` repository.

## Core Architectural Differences

The primary distinction lies in which thread executes the formatting and sink write operations.

### Synchronous Logger Implementation

The synchronous logger is defined in [[`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/logger.h). When a log call occurs, the implementation follows a direct execution path:

1. **Level check** – `should_log(lvl)` validates the message priority against the logger's configured level
2. **Sink dispatch** – `sink_it_(msg)` forwards the fully-formed `log_msg` directly to each registered sink
3. **Formatter invocation** – Each sink applies its own formatter and performs I/O immediately

Because all processing happens in the caller thread, the method blocks until the underlying write operation completes. This design ensures immediate durability but couples logging latency directly to I/O performance.

### Asynchronous Logger Implementation

The asynchronous logger, 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), inherits from `spdlog::logger` but overrides the `sink_it_` method. Instead of direct dispatch, it performs these steps:

1. **Message queuing** – Creates a copy of the `log_msg` and posts it to the thread pool via `thread_pool::post_log`
2. **Background processing** – Workers in [[`include/spdlog/details/thread_pool.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/thread_pool.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/details/thread_pool.h) continuously call `process_next_msg_()` to dequeue messages
3. **Deferred sink writes** – The background thread handles formatting and I/O operations

This architecture decouples the application thread from disk or network latency, but introduces queue management complexity.

## Performance and Threading Characteristics

When comparing **synchronous and asynchronous loggers in spdlog**, consider these operational differences:

- **Execution path** – Synchronous loggers execute entirely in the caller thread; asynchronous loggers pay only the cost of memory allocation and queue insertion before returning control
- **Throughput** – High-frequency logging scenarios benefit from async loggers because the caller thread never waits for disk flushes or network transmissions
- **Ordering guarantees** – Synchronous loggers write messages in exact chronological order. Asynchronous loggers preserve FIFO ordering per-logger, but relative ordering between multiple async loggers sharing a thread pool depends on queue scheduling
- **Resource overhead** – Synchronous mode requires no additional threads or queues. Asynchronous mode requires a `thread_pool` object with a bounded lock-free MPMC queue and associated memory for message storage

## Overflow Handling Policies

Asynchronous loggers must handle queue saturation. The `async_overflow_policy` enum 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) defines three strategies:

- **`block`** – The producer thread waits until space becomes available in the queue
- **`overrun_oldest`** – Discards the oldest queued message to make room for the new entry
- **`discard_new`** – Drops the incoming message if the queue is full

The factory functions in [[`include/spdlog/async.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/async.h) configure these policies during logger creation.

## Practical Code Examples

### Creating a Synchronous Logger

Use the standard factory functions for synchronous operation. The caller blocks on each log call until the sink completes the write.

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

int main() {
    auto logger = spdlog::basic_logger_mt("sync_file", "logs/sync.log");
    
    logger->info("Processed in caller thread");
    logger->debug("Debug data written immediately");
    logger->flush();  // Explicit flush blocks until disk commit
}

```

### Creating an Asynchronous Logger (Blocking)

Use `spdlog::create_async` to initialize a logger with the global thread pool. By default, this uses the `block` overflow policy.

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

int main() {
    auto async_logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>(
        "async_file", "logs/async.log");
    
    async_logger->info("Enqueued for background processing");
    async_logger->debug("Returns immediately to caller");
    async_logger->flush();  // Requests flush from background thread
}

```

The `create_async` function lazy-initializes a global thread pool (default: 8192 queue slots, 1 thread) and returns a `std::shared_ptr<async_logger>`.

### Non-Blocking Asynchronous Logger

Use `spdlog::create_async_nb` to select the `overrun_oldest` policy, ensuring the caller never blocks even under heavy load.

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

int main() {
    auto nb_logger = spdlog::create_async_nb<spdlog::sinks::stdout_color_sink_mt>(
        "async_nb", std::cout);
    
    for (int i = 0; i < 100000; ++i) {
        nb_logger->info("High volume message {}", i);  // May drop oldest if queue full
    }
}

```

## Summary

- **Synchronous loggers** execute `sink_it_` directly in the caller thread, providing immediate durability but blocking on I/O
- **Asynchronous loggers** enqueue messages via `thread_pool::post_log` and process them in background threads, minimizing caller latency
- The `async_overflow_policy` controls behavior when the lock-free queue saturates: blocking, discarding oldest, or discarding new
- Factory functions in [`async.h`](https://github.com/gabime/spdlog/blob/main/async.h) manage the global thread pool lifecycle and overflow configuration
- Choose synchronous mode for simplicity and strict ordering guarantees; choose asynchronous mode for high-throughput scenarios where caller latency must remain predictable

## Frequently Asked Questions

### When should I use asynchronous loggers in spdlog?

Use asynchronous loggers when your application requires high-throughput logging or when log sinks involve high-latency operations like network writes or slow disk I/O. The background thread pool absorbs these delays, preventing the main application thread from stalling. However, if you require immediate confirmation that a message has been persisted to disk, synchronous logging provides stronger durability guarantees.

### Does spdlog guarantee message ordering with async loggers?

Yes, ordering is preserved per-logger on a FIFO basis. The lock-free MPMC queue in [`thread_pool.h`](https://github.com/gabime/spdlog/blob/main/thread_pool.h) ensures that messages posted by a specific `async_logger` instance are processed in the exact order they were enqueued. However, if multiple async loggers share the same thread pool, their relative output ordering depends on the scheduling of the queue consumers and cannot be guaranteed across different logger instances.

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

The behavior depends on the configured `async_overflow_policy`. With the default `block` policy, the calling thread waits until space becomes available. With `overrun_oldest`, the oldest message in the queue is removed to accommodate the new one. With `discard_new`, the new message is dropped silently. You can select the policy using `spdlog::create_async` or `spdlog::create_async_nb` as implemented in [`include/spdlog/async.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/async.h).

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

Yes. spdlog allows simultaneous use of both logger types. Synchronous loggers instantiated via `spdlog::basic_logger_mt` operate independently, while asynchronous loggers created through `spdlog::create_async` share the global thread pool managed by the async factory. This flexibility enables you to use async logging for high-volume debug output while maintaining synchronous logging for critical audit trails that require immediate persistence.