# How to Configure spdlog's Periodic Flush Feature and Understand Thread-Safety Implications

> Configure spdlog's periodic flush feature using flush_every and understand its thread-safe design. Learn how background threads ensure efficient, secure logging.

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

---

**Call `spdlog::flush_every(std::chrono::duration)` to start a background thread that flushes all registered loggers at fixed intervals; thread safety is guaranteed because every sink inherits from `base_sink`, which protects `flush()` with the same mutex used for `log()`.**

The **spdlog** library provides a `periodic flush` mechanism that automatically persists buffered log data without requiring manual `logger->flush()` calls. This feature spawns a dedicated background thread that repeatedly invokes a flush callback at user-defined intervals. Below is a complete guide to configuring this feature and understanding its multithreaded behavior based on the `gabime/spdlog` source code.

---

## Enabling Periodic Flush with `spdlog::flush_every()`

The public API entry point is `spdlog::flush_every()` in [`include/spdlog/spdlog.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog.h). This function forwards the request to the global registry singleton:

```cpp
// include/spdlog/spdlog.h (lines 80-87)
template<typename Rep, typename Period>
inline void flush_every(std::chrono::duration<Rep, Period> interval)
{
    details::registry::instance().flush_every(interval);
}

```

The registry stores the interval and creates a `periodic_worker` instance that encapsulates the background thread logic. To start flushing every 5 seconds:

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

int main() {
    auto file_logger = spdlog::basic_logger_mt("file_logger", "app.log");
    
    // Start periodic flush: every 5 seconds
    spdlog::flush_every(std::chrono::seconds(5));
    
    // Normal logging operations...
}

```

---

## How the Background Thread Works

The `periodic_worker` class in [`include/spdlog/details/periodic_worker.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/periodic_worker.h) manages the dedicated flush thread. Its implementation follows a standard condition variable pattern for periodic wakeups.

### Thread Lifecycle

**Construction** starts the thread immediately:

```cpp
// include/spdlog/details/periodic_worker-inl.h (lines 31-38)
SPDLOG_INLINE periodic_worker::periodic_worker(
    const std::function<void()> &callback_fun, 
    interval_t interval)
{
    if (interval == interval_t::zero()) { return; }
    
    active_ = true;
    worker_thread_ = std::thread([this, callback_fun, interval]() {
        std::unique_lock<std::mutex> lock(mutex_);
        while (active_) {
            if (cv_.wait_for(lock, interval) == std::cv_status::timeout) {
                callback_fun();  // Calls registry::flush_all()
            }
        }
    });
}

```

**Destruction** signals termination and joins cleanly:

```cpp
// include/spdlog/details/periodic_worker-inl.h (lines 14-22)
SPDLOG_INLINE periodic_worker::~periodic_worker()
{
    if (worker_thread_.joinable()) {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            active_ = false;
        }
        cv_.notify_one();
        worker_thread_.join();
    }
}

```

The registry holds the worker in a `std::unique_ptr<periodic_worker>`, allowing safe reset when reconfiguring or shutting down.

---

## Stopping or Reconfiguring the Flush Interval

To **change the interval**, simply call `flush_every()` again. The registry resets the worker and creates a new one with the updated duration:

```cpp
// include/spdlog/details/registry.h (lines 71-74)
void registry::flush_every(interval_t interval)
{
    std::lock_guard<std::mutex> lock(flusher_mutex_);
    auto clbk = [this]() { this->flush_all(); };
    periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval);
}

```

To **stop periodic flushing entirely**, pass a zero duration:

```cpp
spdlog::flush_every(std::chrono::seconds(0));  // Stops and joins the thread

```

For complete cleanup at program exit, follow with `spdlog::shutdown()`:

```cpp
spdlog::flush_every(std::chrono::seconds(0));
spdlog::shutdown();  // Closes all loggers and releases resources

```

---

## Thread-Safety Guarantees

The periodic flush mechanism is **thread-safe by design** through two layers of synchronization.

### Sink-Level Mutex Protection

Every sink implementation inherits from `base_sink<Mutex>` (typically `base_sink<std::mutex>`). This base class protects both logging and flushing with the same mutex:

```cpp
// include/spdlog/sinks/base_sink.h (lines 32-38)
void log(const details::log_msg &msg) override
{
    std::lock_guard<Mutex> lock(sink_mutex_);
    sink_it_(msg);
}

void flush() override
{
    std::lock_guard<Mutex> lock(sink_mutex_);
    flush_();
}

```

Because `sink_mutex_` guards both `sink_it_()` (called during normal logging) and `flush_()` (called by the periodic worker), **no race conditions exist** between log writes and flush operations. The periodic flush simply blocks briefly until any concurrent log operation completes.

### Logger and Registry Safety

The flush callback invokes `registry::flush_all()`, which iterates over registered loggers and calls each logger's `flush_()` method. In [`include/spdlog/logger-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger-inl.h), this iterates over the logger's sinks and calls `sink->flush()` on each. Since all standard sinks derive from `base_sink`, each flush call acquires the appropriate lock automatically.

The periodic worker thread operates **independently** of user threads:
- It does not interfere with logger creation or destruction
- It does not block log formatting or queueing (only the final sink operation)
- It is safely joined during registry destruction or explicit reconfiguration

---

## When to Use Periodic Flush

**Recommended for:**
- **File-based sinks** (`basic_file_sink`, `rotating_file_sink`, `daily_file_sink`) that buffer OS-level writes
- **Custom network sinks** that batch transmissions for efficiency
- Long-running applications where crash safety for recent logs is critical

**Unnecessary for:**
- Sinks with `force_flush = true` (e.g., `stdout_sink` configured for immediate output)
- Asynchronous loggers using the `async_logger` with dedicated queues—these have their own flush mechanisms

---

## Complete Working Example

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <chrono>

int main() {
    // Create rotating file sink: 5 MB size, 3 rotated files
    auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
        "app.log", 5 * 1024 * 1024, 3);
    
    auto logger = std::make_shared<spdlog::logger>("main", rotating_sink);
    spdlog::register_logger(logger);
    
    // Flush every 10 seconds to balance performance and durability
    spdlog::flush_every(std::chrono::seconds(10));
    
    // Production logging loop
    for (int i = 0; i < 10000; ++i) {
        logger->info("Processing batch {} with {} items", i, i * 100);
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
    
    // Graceful shutdown sequence
    spdlog::flush_every(std::chrono::seconds(0));  // Stop periodic worker
    spdlog::shutdown();                            // Flush and close all sinks
    
    return 0;
}

```

---

## Comparison: Periodic Flush vs. Level-Based Flush

spdlog offers two complementary flush strategies:

| Mechanism | API | Trigger | Use Case |
|-----------|-----|---------|----------|
| **Periodic flush** | `spdlog::flush_every(duration)` | Time interval | Regular durability guarantees with minimal overhead |
| **Level-based flush** | `spdlog::flush_on(spdlog::level::warn)` | Log level threshold | Immediate persistence for critical errors |

These can be **combined**: periodic flush for routine batches plus level-based flush for urgent messages.

---

## Summary

- **Enable** periodic flush with `spdlog::flush_every(std::chrono::duration)`—the background thread starts automatically via `periodic_worker`
- **Reconfigure** by calling `flush_every()` again with a new duration; **stop** with a zero duration
- **Thread safety** is enforced through `base_sink`'s `sink_mutex_`, which serializes `log()` and `flush()` operations per sink
- **Cleanup** requires explicit `flush_every(0s)` followed by `spdlog::shutdown()` for orderly thread termination
- **Best suited** for buffered sinks where durability must not depend on manual flush calls or program termination

---

## Frequently Asked Questions

### How do I completely disable periodic flushing once started?

Call `spdlog::flush_every(std::chrono::seconds(0))`. According to the `periodic_worker` constructor in [`include/spdlog/details/periodic_worker-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/periodic_worker-inl.h) line 34, a zero interval causes early return without thread creation. The registry's `flush_every` implementation (lines 71-74 in [`registry.h`](https://github.com/gabime/spdlog/blob/main/registry.h)) resets the `unique_ptr`, invoking the destructor which joins any running thread.

### Does periodic flush affect logging performance?

The impact is **minimal and bounded**. The periodic worker only contends for the `sink_mutex` during its brief flush window. Normal logging operations acquire the same mutex but release it immediately after the sink operation. The background thread sleeps on a condition variable between intervals, consuming no CPU.

### Can I use periodic flush with asynchronous loggers?

Yes, but understand the interaction. Asynchronous loggers (`async_logger`) queue messages to a separate thread pool. The periodic flush still operates on the **sinks**, not the queue. For async loggers, you typically want `spdlog::flush_every()` to ensure sink buffers drain, while queue overflow is handled by the async worker's queue policy.

### Why are my logs still missing after a crash with periodic flush?

The periodic flush guarantees data reaches the **sink's** flush operation, but OS-level buffering may still delay physical disk writes. For maximum durability, combine periodic flush with `std::flush` behavior in your sink configuration or use `O_DSYNC`/`O_DIRECT` file flags at the OS level.