Flush on vs Flush Every in spdlog: When to Flush Logs for Maximum Reliability

Use flush_on(level) to immediately persist critical messages when they reach a specific severity threshold, and flush_every(duration) to periodically flush all buffers on a fixed schedule without per-message overhead.

Choosing the right flushing strategy in gabime/spdlog is essential for balancing I/O performance with data durability. The library provides two distinct mechanisms for controlling when buffered log data is written to sinks. Understanding when to flush logs in spdlog ensures that critical errors are never lost while maintaining high throughput for routine logging operations.

Understanding flush_on vs flush_every

flush_on: Per-Message Flushing

The flush_on(level) function sets a global flush level that applies to every logger in the registry. When a log record's severity is greater than or equal to this threshold, the logger immediately calls flush() on all its sinks before returning. This guarantees that critical messages are persisted to disk instantly, while lower-level traffic remains buffered for performance.

According to the source code in include/spdlog/logger.h (lines 300–303) and include/spdlog/logger-inl.h (lines 100–103), flush_on performs a simple atomic store:

// spdlog/logger.h
void flush_on(level::level_enum log_level);

// spdlog/logger-inl.h
SPDLOG_INLINE void logger::flush_on(level::level_enum log_level) { 
    flush_level_.store(log_level); 
}

During each log call, the method should_flush_() (defined in logger-inl.h lines 166–167) checks if msg.level >= flush_level(). If true, logger::flush_() iterates over the logger’s sinks and invokes sink->flush() (logger-inl.h lines 148–150).

flush_every: Periodic Flushing

The flush_every(duration) function starts a background thread that calls flush() on all registered loggers at fixed intervals, regardless of message level. This is implemented as a thin wrapper around the registry’s periodic flusher (see include/spdlog/spdlog.h lines 83–88 and include/spdlog/details/registry.h line 68):

// spdlog/spdlog.h
template <typename Rep, typename Period>
inline void flush_every(std::chrono::duration<Rep, Period> interval) {
    details::registry::instance().flush_every(interval);
}

The registry spawns a thread that sleeps for the requested interval and then executes apply_all([](auto l){ l->flush(); }), ensuring that all buffered logs are persisted on a predictable schedule.

Performance and Thread Safety Considerations

Performance Impact:

  • flush_on adds only a cheap atomic compare per log entry; the expensive I/O operation happens only when high-severity messages arrive.
  • flush_every incurs the cost of a background thread and triggers a global flush at each tick, which can become expensive if sinks are numerous or slow (e.g., remote network sinks).

Thread Safety: flush_every requires that all loggers be thread-safe. As noted in spdlog.h line 84, if any logger uses a non-thread-safe sink (such as a non-locking stdout sink), the periodic flusher may race with user threads. In contrast, flush_on respects each sink’s existing thread-safety guarantees because flushes are triggered synchronously by the calling thread.

When to Use Which Strategy

  1. Critical Reliability: If losing the latest error messages is unacceptable, configure spdlog::flush_on(spdlog::level::err) or level::critical. This ensures that any error is immediately flushed, while ordinary info and debug traffic stays buffered.

  2. Regular Durability Without Overhead: For long-lived services that can tolerate a few seconds of delay, use spdlog::flush_every(std::chrono::seconds(5)). This provides predictable persistence without the per-message cost of flush_on.

  3. Combined Strategy: It is common to use both mechanisms simultaneously. Set flush_on(level::critical) for urgent failures, and flush_every(std::chrono::seconds(10)) to guarantee that lower-level logs are persisted periodically.

Code Examples

Example 1: Flush Immediately on Error or Higher

#include "spdlog/spdlog.h"

int main() {
    spdlog::set_level(spdlog::level::info);          // global log level
    spdlog::flush_on(spdlog::level::err);           // immediate flush for errors

    auto logger = spdlog::stdout_color_mt("mylog");
    logger->info("normal message");                 // buffered
    logger->error("something went wrong!");         // forces flush
}

Example 2: Periodic Flushing Every 5 Seconds

#include "spdlog/spdlog.h"

int main() {
    spdlog::flush_every(std::chrono::seconds(5));   // starts background flusher

    auto file_logger = spdlog::basic_logger_mt("filelog", "app.log");
    file_logger->debug("debug line");               // will be flushed within 5s
    
    // Keep main alive to see periodic flushing
    std::this_thread::sleep_for(std::chrono::seconds(6));
}

Example 3: Combine Both Strategies

#include "spdlog/spdlog.h"

int main() {
    spdlog::flush_on(spdlog::level::critical);      // only critical messages force immediate flush
    spdlog::flush_every(std::chrono::seconds(10));  // otherwise flush every 10s

    auto logger = spdlog::basic_logger_mt("combined", "app.log");
    logger->info("routine operation");              // flushed within 10s
    logger->critical("system failure!");            // flushed immediately
}

Summary

  • flush_on triggers immediate I/O when messages meet a severity threshold, ensuring critical logs are never lost.
  • flush_every uses a background thread to flush all loggers on a fixed schedule, reducing per-message overhead.
  • Combining both strategies provides immediate persistence for errors alongside periodic durability for routine logs.
  • Always verify thread-safety when using flush_every, as it iterates over all loggers from a separate thread.

Frequently Asked Questions

What is the difference between flush_on and flush_every in spdlog?

flush_on checks every log message's severity and flushes immediately if it meets the threshold, while flush_every spawns a background thread that flushes all loggers at fixed time intervals regardless of message content. The former is event-driven; the latter is time-driven.

Does flush_on affect all loggers in spdlog?

Yes, flush_on sets a global flush level in the registry that applies to all registered loggers. Each logger checks this global level via an atomic load before deciding whether to flush its sinks.

Is flush_every thread-safe in spdlog?

flush_every is safe only if all registered loggers use thread-safe sinks. The periodic flusher calls flush() on every logger from a background thread, which will race with user threads if any sink lacks locking (e.g., stdout_color_sink without mt).

Can I use flush_on and flush_every together in the same application?

Yes, combining both is a recommended pattern. Use flush_on(spdlog::level::err) to guarantee immediate persistence of errors, and flush_every(std::chrono::seconds(10)) to ensure that lower-severity messages are eventually written without paying the I/O cost on every log call.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →