How to Configure Multiple Sinks in spdlog: A Complete Guide
To configure multiple sinks in spdlog, instantiate concrete sink objects such as stdout_color_sink_mt and basic_file_sink_mt, aggregate them using either a std::vector<sink_ptr> passed to the spdlog::logger constructor or a dist_sink_mt wrapper, and register the resulting logger with spdlog::register_logger.
The spdlog library provides a high-performance, header-only logging solution for C++ applications. Its flexible sink architecture allows a single logger to dispatch messages to multiple destinations—such as the console, files, or external systems—simultaneously with independent formatting and severity filtering.
Understanding the Multi-Sink Architecture
The Core Logger Implementation
In include/spdlog/logger.h, the spdlog::logger class maintains a std::vector<std::shared_ptr<spdlog::sinks::sink>> member that stores all attached sinks. When you invoke a logging method like logger->info(), the implementation iterates over this vector and forwards the formatted message to each sink's sink_it_() method.
The Distribution Sink Pattern
For scenarios requiring dynamic sink management, spdlog provides spdlog::sinks::dist_sink defined in include/spdlog/sinks/dist_sink.h. This composite sink owns a collection of child sinks and forwards each log request to all children, preserving their individual set_pattern() and set_level() configurations.
Creating Concrete Sink Instances
Each sink derives from the abstract base class in include/spdlog/sinks/sink.h and implements the sink_it_() and flush_() virtual methods.
Console sink example from include/spdlog/sinks/stdout_color_sinks.h:
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::info);
console_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v");
File sink example from include/spdlog/sinks/basic_file_sink.h:
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/app.log", true);
file_sink->set_level(spdlog::level::debug);
file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");
Aggregating Sinks Using dist_sink
The dist_sink approach simplifies logger construction and enables runtime modification. Instead of passing a vector directly to the logger constructor, you wrap sinks in a distribution sink:
auto dist_sink = std::make_shared<spdlog::sinks::dist_sink_mt>();
dist_sink->add_sink(console_sink);
dist_sink->add_sink(file_sink);
auto logger = std::make_shared<spdlog::logger>("multi_sink", dist_sink);
spdlog::register_logger(logger);
Alternatively, pass a vector directly to avoid the wrapper overhead:
std::vector<spdlog::sink_ptr> sinks{console_sink, file_sink};
auto logger = std::make_shared<spdlog::logger>("multi_sink", sinks.begin(), sinks.end());
Complete Implementation Example
The following example demonstrates configuring both console and file output with distinct formatting and log levels, as implemented in the spdlog source:
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/dist_sink.h>
void setup_multi_sink_logger() {
// Create console sink with color support
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::info);
console_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v");
// Create file sink with append mode
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/app.log", true);
file_sink->set_level(spdlog::level::debug);
file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");
// Combine using dist_sink for runtime flexibility
auto dist_sink = std::make_shared<spdlog::sinks::dist_sink_mt>();
dist_sink->add_sink(console_sink);
dist_sink->add_sink(file_sink);
// Initialize logger with global debug level
auto logger = std::make_shared<spdlog::logger>("multi_sink", dist_sink);
logger->set_level(spdlog::level::debug);
spdlog::register_logger(logger);
// Usage: info goes to both, debug only to file
logger->info("Application startup complete");
logger->debug("Debug details visible only in file");
}
Advanced Configuration Patterns
Per-Sink Log Level Filtering
When you configure multiple sinks in spdlog, each sink maintains its own severity threshold via set_level(). Messages below a sink's threshold are silently discarded for that destination, allowing fine-grained control—such as verbose debug logs in files while restricting console output to warnings and errors.
Runtime Sink Management
Using dist_sink enables dynamic modification of the sink collection after logger creation. Call dist_sink->add_sink(new_sink) or dist_sink->remove_sink(existing_sink) to alter destinations without reconstructing the logger object, as the underlying vector in include/spdlog/sinks/dist_sink.h supports thread-safe modifications when using the _mt variants.
Summary
- Vector-based storage: The
spdlog::loggerclass ininclude/spdlog/logger.hstores sinks in astd::vector<std::shared_ptr<sink>>, dispatching each log message to every attached sink. - Concrete implementations: Sinks like
stdout_color_sink_mtandbasic_file_sink_mtderive from the base class ininclude/spdlog/sinks/sink.hand implementsink_it_()for specific destinations. - Aggregation strategies: Use
spdlog::sinks::dist_sinkfrominclude/spdlog/sinks/dist_sink.hfor convenient runtime management, or pass a sink vector directly to the logger constructor for minimal overhead. - Independent configuration: Each sink supports individual pattern formatting and log level filtering, allowing console and file outputs to display different information densities.
Frequently Asked Questions
Can I add or remove sinks after creating the logger?
Yes. If you use spdlog::sinks::dist_sink as the logger's single sink, you can call add_sink() or remove_sink() on the distribution sink at any time to modify the destination collection without recreating the logger object. This is not possible when passing a raw vector to the logger constructor, as that vector is copied during initialization.
How do I set different log levels for different sinks?
Each concrete sink exposes a set_level() method. When configuring multiple sinks, call this method on each sink individually before adding them to the logger. For example, set the console sink to spdlog::level::warn and the file sink to spdlog::level::debug to suppress debug messages in the terminal while preserving them in the log file.
What is the difference between dist_sink and passing a vector directly?
The dist_sink approach wraps your sinks in a composite object that implements the sink interface itself, allowing runtime modifications and treating the collection as a single entity. Passing a std::vector<sink_ptr> directly to the spdlog::logger constructor stores the sinks internally without the extra indirection layer, offering marginally better performance but static configuration after construction.
Which header files do I need to include for multi-sink logging?
Include spdlog/spdlog.h for the core API, plus specific sink headers such as spdlog/sinks/stdout_color_sinks.h for console output and spdlog/sinks/basic_file_sink.h for file logging. If using the distribution pattern, include spdlog/sinks/dist_sink.h. The base sink interface resides in spdlog/sinks/sink.h.
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 →