spdlog Sink Performance: A Complete Guide to Choosing Fast Logging Backends

The performance of spdlog sinks varies dramatically based on thread-safety model, I/O cost, and extra processing—ranging from zero-cost null_sink (no-op) to high-overhead rotating file sinks with mutex contention and filesystem operations.

Spdlog routes every log record through a sink, an object that determines where and how formatted messages are written. The sink implementation you choose directly impacts application throughput, latency, and scalability. This guide examines the performance implications of different spdlog sink implementations based on the actual source code in gabime/spdlog.

Thread-Safety Models: Synchronous vs. Asynchronous

Spdlog sinks fall into two architectural categories that fundamentally affect performance under concurrency.

Synchronous Sinks: Mutex-Per-Log

All synchronous sinks inherit from spdlog::sinks::base_sink<Mutex> in include/spdlog/sinks/base_sink.h. The critical path involves acquiring a mutex on every log call:

template <typename Mutex>
void base_sink<Mutex>::log(const details::log_msg &msg) {
    std::lock_guard<Mutex> lock(mutex_);
    sink_it_(msg);
}

This design serializes access to the underlying output resource. Contention increases linearly with thread count, making synchronous sinks unsuitable for high-frequency, multi-threaded logging without careful consideration.

Asynchronous Sinks: Lock-Free Queuing

The async_factory in include/spdlog/async.h creates loggers that enqueue messages in a lock-free ring buffer. A background thread pool dequeues and writes to the actual sinks:

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

auto logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>(
                 "async_file_logger", "logs/async.txt");
  • Pros: Producer threads never block on sink mutexes; excellent scalability
  • Cons: Added latency (async drain time); memory overhead for message queue

File Sink Performance in spdlog

File sinks vary significantly in overhead based on rotation logic and size checking.

basic_file_sink: Minimal Overhead

Source files: include/spdlog/sinks/basic_file_sink.h, include/spdlog/sinks/basic_file_sink-inl.h

The fastest file sink performs a direct write without size checks:

#include <spdlog/sinks/basic_file_sink.h>

auto file_logger = spdlog::basic_logger_mt("file_logger", "logs/app.log");
file_logger->info("Synchronous file log – fast, no rotation");

In basic_file_sink-inl.h, the implementation calls file_helper_.write(formatted) directly—no additional processing between format and I/O.

rotating_file_sink: Rotation Cost

Source files: include/spdlog/sinks/rotating_file_sink.h, include/spdlog/sinks/rotating_file_sink-inl.h

Every write triggers a size check (new_size > max_size_). When rotation occurs, rotate_() performs filesystem renames and, on Windows, a sleep_for_millis(100) delay (lines 16-63):

#include <spdlog/sinks/rotating_file_sink.h>

auto rotating = spdlog::rotating_logger_mt(
    "rot_logger", "logs/rot.log", 
    /*max size*/ 10 * 1024 * 1024, 
    /*max files*/ 5);

Performance impact: CPU and filesystem latency spikes at rotation boundaries. Use async wrapper to isolate this contention from producer threads.

Daily and Hourly File Sinks: Time-Based Overhead

Source files: include/spdlog/sinks/daily_file_sink.h, include/spdlog/sinks/hourly_file_sink.h

These sinks query the system clock on every write to detect rotation boundaries. The hourly variant incurs this cost more frequently than the daily variant. Both add small but measurable overhead compared to basic_file_sink.

Console Sink Performance Characteristics

Console output involves terminal drivers and system calls that dominate performance.

Sink Source File Performance Notes
stdout_sink stdout_sinks.h Writes via fwrite to std::cout; terminal driver is the bottleneck
stdout_color_sink stdout_color_sinks.h Adds ANSI color codes; minimal string processing overhead
ansicolor_sink ansicolor_sink.h Windows console API calls add extra system calls for attribute setting

Example color console usage:

#include <spdlog/sinks/stdout_color_sinks.h>

auto console = spdlog::stdout_color_mt("console");
console->error("Error printed in red on the terminal");

Console I/O is inherently slower than file I/O due to terminal rendering, screen buffer management, and synchronized console access on Windows.

Specialized Sinks: Eliminating or Transforming Work

null_sink: Zero-Cost Logging

Source file: include/spdlog/sinks/null_sink.h

The sink_it_() method is a no-op—no I/O, no allocation, no serialization:

#include <spdlog/sinks/null_sink.h>

auto silent = std::make_shared<spdlog::sinks::null_sink_mt>();
auto logger = std::make_shared<spdlog::logger>("null_logger", silent);

Use null_sink for maximum throughput benchmarking or when you need to compile out logging in performance-critical paths without removing call sites.

dup_filter_sink: Deduplication Trade-off

Source file: include/spdlog/sinks/dup_filter_sink.h

This sink maintains a hash map of recent messages to suppress duplicates. The overhead:

  • Hash computation and map lookup per log call
  • Memory allocation for map entries
  • Cache pressure from map traversal

Acceptable when deduplication dramatically reduces downstream I/O volume.

dist_sink: Multi-Destination Fan-out

Source file: include/spdlog/sinks/dist_sink.h

Forwards each message to multiple child sinks. Performance equals the sum of all child sink costs plus dispatch overhead. Combine strategically:

// Fast path: null_sink + backup file
dist_sink->add_sink(std::make_shared<null_sink_mt>());      // Discard
dist_sink->add_sink(std::make_shared<basic_file_sink_mt>(path)); // Preserve

Performance-Guided Sink Selection Matrix

Use Case Recommended Sink Rationale
Maximum throughput, benchmarking null_sink No I/O, no locks, no allocation
High-frequency production logging basic_file_sink_mt + async factory Lock-free queuing eliminates contention; minimal per-message work
Rotating logs with many threads rotating_file_sink_mt + async factory Rotation cost isolated to background thread
Human-readable development output stdout_color_sink_mt Console I/O bottleneck dominates; simplicity preferred
Deduplication of noisy errors dup_filter_sink_mt wrapping file sink Hash overhead justified by reduced write volume
Selective multi-destination logging dist_sink_mt with null_sink + basic_file_sink_mt Discard non-critical, preserve errors cheaply

Implementation Details from Source Code

Key performance-relevant files in gabime/spdlog:

Summary

  • Lock contention dominates synchronous sink performance—use async_factory for multi-threaded, high-frequency logging
  • I/O cost hierarchy: null_sink (none) < file sinks < console sinks (highest)
  • Feature overhead: Rotation, filtering, and distribution add measurable per-message cost; enable only when functionally required
  • Optimal combination: Async logger with basic_file_sink_mt provides excellent throughput for production workloads

Frequently Asked Questions

How much faster is null_sink compared to file sinks?

null_sink eliminates all I/O and most processing overhead. In throughput benchmarks, it typically achieves 10-100x higher message rates than file sinks and 100-1000x higher than console sinks, making it ideal for measuring formatting overhead independent of I/O.

Should I always use async loggers for multi-threaded applications?

Not always. Async loggers add latency (messages are not immediately written) and memory overhead for the message queue. For moderate logging frequencies with few threads, synchronous basic_file_sink_mt may suffice. Use async when you observe mutex contention or require maximum producer thread throughput.

Why does rotating_file_sink have a 100ms sleep on Windows?

The sleep_for_millis(100) in rotating_file_sink-inl.h (line 63) ensures filesystem consistency during rename operations on Windows, where file handles may not release immediately. This adds latency spikes during rotation but prevents data corruption.

Can I combine multiple sinks without linear performance degradation?

With dist_sink, overhead is additive. However, you can optimize by placing cheap sinks (like null_sink) before expensive ones and using dup_filter_sink to skip redundant work. Structure the sink chain so that expensive operations are gated by cheaper filters.

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 →