# How to Create a Logger with Multiple Sinks in spdlog

> Learn how to create a logger with multiple sinks in spdlog. Effortlessly direct log messages to various destinations using vectors or the dist_sink for comprehensive logging control.

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

---

**In spdlog, you create a multi-sink logger by either passing a vector of `std::shared_ptr<spdlog::sinks::sink>` to the `spdlog::logger` constructor or by wrapping multiple child sinks in a `spdlog::sinks::dist_sink`, which broadcasts every log message to all attached destinations.**

The spdlog library (gabime/spdlog) routes log messages through the *sink* abstraction, allowing a single logger instance to write to multiple destinations simultaneously. Understanding how to create a logger with multiple sinks in spdlog enables you to stream identical output to both the console and persistent log files while maintaining independent formatting and severity filters for each target.

## Understanding the Multi-Sink Architecture

### The Logger Class ([`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h))

According to the spdlog source code, the `spdlog::logger` class maintains a `std::vector<std::shared_ptr<spdlog::sinks::sink>>` to store its attached sinks. When you invoke a logging method such as `logger->info()`, the logger iterates over this container and forwards the formatted message to each sink's `sink_it_` implementation. This design means a logger naturally supports multiple sinks without additional wrapper code.

### The Distribution Sink ([`include/spdlog/sinks/dist_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/dist_sink.h))

For scenarios requiring dynamic sink management, spdlog provides `spdlog::sinks::dist_sink`. This convenience sink holds its own collection of child sinks and implements the `sink_it_` method to forward every log request to all children. It preserves individual `set_level` and `set_pattern` configurations for each child, allowing runtime addition or removal via `add_sink()` and `remove_sink()`.

## Method 1: Direct Vector Initialization

The most straightforward approach constructs a vector of sink pointers and passes the iterator range directly to the logger constructor. This method minimizes overhead and works best when your sink configuration remains static after initialization.

```cpp
#include <spdlog/logger.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <vector>

// Create concrete sinks with independent configurations
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::info);
console_sink->set_pattern("[%H:%M:%S] [%^%l%$] %v");

auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log", true);
file_sink->set_level(spdlog::level::debug);
file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");

// Construct logger with begin/end iterators
std::vector<spdlog::sink_ptr> sinks{console_sink, file_sink};
auto logger = std::make_shared<spdlog::logger>("multi_sink", sinks.begin(), sinks.end());

logger->set_level(spdlog::level::debug);
spdlog::register_logger(logger);

```

## Method 2: Using `dist_sink` for Runtime Flexibility

Use `spdlog::sinks::dist_sink` when you need to modify the sink collection after logger creation. This pattern delegates sink management to the distribution sink while presenting a single interface to the logger.

```cpp
#include <spdlog/sinks/dist_sink.h>

// Initialize sinks as before
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/app.log", true);

// Aggregate in 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);

// Logger receives the single dist_sink
auto logger = std::make_shared<spdlog::logger>("distributed", dist_sink);
logger->set_level(spdlog::level::debug);

// Runtime modification: remove file sink without reconstructing logger
// dist_sink->remove_sink(file_sink);

```

## Configuring Independent Sink Behavior

Each concrete sink derives from `spdlog::sinks::sink` (defined in [`include/spdlog/sinks/sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/sink.h)) and implements the pure virtual methods `sink_it_` and `flush_`. This inheritance allows every sink in a multi-sink configuration to maintain its own:

- **Log level filter**: Controlled via `set_level(spdlog::level::level_enum)`
- **Output pattern**: Controlled via `set_pattern(const std::string&)`
- **Flush behavior**: Triggered via `flush()` or automatic flush intervals

When a log message reaches a sink with a severity below its configured threshold, that sink silently discards the message while other sinks in the chain continue processing.

## Complete Working Example

The following implementation demonstrates combining console and file sinks with distinct patterns, as shown in the official repository:

```cpp
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/basic_file_sink.h"
#include <vector>

void multi_sink_example()
{
    // Console sink with ANSI 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("[%H:%M:%S %z] [%^%l%$] %v");

    // File sink with append mode enabled
    auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true);
    file_sink->set_level(spdlog::level::debug);

    // Initialize logger with sink vector
    spdlog::logger logger("multi_sink", {console_sink, file_sink});
    logger.set_level(spdlog::level::debug);
    
    // Messages dispatch to both sinks
    logger.info("Application started");      // Appears in console and file
    logger.debug("Debug details");           // Appears only in file (console filters at info)
}

```

## Summary

- **Core mechanism**: `spdlog::logger` stores sinks in a `std::vector<std::shared_ptr<spdlog::sinks::sink>>` defined in [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h), iterating through the collection on every log call.
- **Construction options**: Pass a vector iterator range directly to the logger constructor, or aggregate sinks within `spdlog::sinks::dist_sink` (from [`include/spdlog/sinks/dist_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/dist_sink.h)) for runtime modification.
- **Per-sink configuration**: Each sink maintains independent log levels and formatting patterns, allowing the console to show warnings while a file captures debug messages.
- **Concrete implementations**: Common sinks include `stdout_color_sink_mt` for colored console output and `basic_file_sink_mt` for file persistence, both implementing the `sink_it_` method.

## Frequently Asked Questions

### How do I create a logger with multiple sinks in spdlog?

You instantiate concrete sink classes such as `spdlog::sinks::stdout_color_sink_mt` and `spdlog::sinks::basic_file_sink_mt`, add them to a `std::vector<spdlog::sink_ptr>`, and pass the vector's begin and end iterators to the `spdlog::logger` constructor. Alternatively, wrap the sinks in a `spdlog::sinks::dist_sink` and pass that single sink to the logger.

### What is the difference between using a vector of sinks and `dist_sink` in spdlog?

When you pass a vector directly to `spdlog::logger`, the logger iterates through the sinks itself. When using `dist_sink`, the logger treats the distribution sink as a single target, and the `dist_sink` forwards messages to its internal collection. The `dist_sink` approach allows runtime sink addition and removal via `add_sink()` and `remove_sink()`, whereas the vector approach requires logger reconstruction to modify the sink list.

### Can each sink have a different log level when using multiple sinks in spdlog?

Yes. Each sink inherits the `level_` member from `spdlog::sinks::sink` and filters independently. Configure individual thresholds using `sink->set_level()` before adding the sink to the logger or `dist_sink`. The logger dispatches messages to all sinks, and each sink applies its own filter.

### Where can I find official examples of multi-sink loggers in spdlog?

The official gabime/spdlog repository includes working implementations in [`example/example.cpp`](https://github.com/gabime/spdlog/blob/main/example/example.cpp), which demonstrates creating loggers with both console and file outputs. Header definitions for sink classes are located in [`include/spdlog/sinks/stdout_color_sinks.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/stdout_color_sinks.h) for console output and [`include/spdlog/sinks/basic_file_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/basic_file_sink.h) for file logging.