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_onadds only a cheap atomic compare per log entry; the expensive I/O operation happens only when high-severity messages arrive.flush_everyincurs 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
-
Critical Reliability: If losing the latest error messages is unacceptable, configure
spdlog::flush_on(spdlog::level::err)orlevel::critical. This ensures that any error is immediately flushed, while ordinaryinfoanddebugtraffic stays buffered. -
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 offlush_on. -
Combined Strategy: It is common to use both mechanisms simultaneously. Set
flush_on(level::critical)for urgent failures, andflush_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_ontriggers immediate I/O when messages meet a severity threshold, ensuring critical logs are never lost.flush_everyuses 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →