# Creating an spdlog Logger with Multiple Sinks: A Complete Guide

> Learn to create an spdlog logger with multiple sinks. Aggregate destinations like stdout and files, then pass them to the spdlog logger constructor for comprehensive logging.

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

---

**You create an spdlog logger with multiple sinks by instantiating concrete destination objects (such as `stdout_color_sink` and `basic_file_sink`), aggregating them using either a `dist_sink` wrapper or a `std::vector`, and passing the collection to the `spdlog::logger` constructor so that every log call is broadcast to every attached destination.**

Creating an spdlog logger with multiple sinks allows you to route identical log messages to several outputs—such as the console and a log file—simultaneously. The `gabime/spdlog` library implements this through a flexible sink architecture where the `spdlog::logger` class maintains a collection of sink objects and dispatches each formatted message to every sink in the collection.

## How Multi-Sink Logging Works in spdlog

The core mechanism lives in **[`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h)**. The `spdlog::logger` class stores sinks in a `std::vector<std::shared_ptr<spdlog::sinks::sink>>`. When you invoke a logging method like `logger->info()`, the logger iterates over this internal vector and calls `sink->log()` on each entry, forwarding the formatted message.

Each concrete sink (located in `include/spdlog/sinks/`) derives from the abstract base class `spdlog::sinks::sink` and implements `sink_it_()` and `flush_()`. Because sinks are independent, they can each enforce their own log levels and output patterns even when attached to the same logger.

## Method 1: Using the Distribution Sink (`dist_sink`)

The **`spdlog::sinks::dist_sink`** (defined in [`include/spdlog/sinks/dist_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/dist_sink.h)) provides a convenient wrapper that itself acts as a sink while owning a collection of child sinks. It forwards every log request to all its children, preserving their individual formatting and level settings.

This approach simplifies logger construction and allows you to add or remove sinks at runtime after the logger is created.

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

// Create individual sinks
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");

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");

// Wrap them 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);

// Create the logger
auto logger = std::make_shared<spdlog::logger>("multi_sink", dist_sink);
logger->set_level(spdlog::level::debug);
spdlog::register_logger(logger);

```

## Method 2: Passing a Sink Vector Directly

Alternatively, you can bypass `dist_sink` and pass an iterator range directly to the `spdlog::logger` constructor. This method stores the sinks directly in the logger's internal vector without an intermediary wrapper.

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

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);

// Collect sinks in a vector
std::vector<spdlog::sink_ptr> sinks{console_sink, file_sink};

// Pass begin/end iterators to constructor
auto logger = std::make_shared<spdlog::logger>("multi_sink", sinks.begin(), sinks.end());
logger->set_level(spdlog::level::debug);
spdlog::register_logger(logger);

```

## Configuring Independent Sink Behavior

One advantage of multiple sinks is **per-sink log levels and formatting**. In the examples above, the console sink shows only `info` and above with colorized output, while the file sink captures `debug` and above with a plain text format. This filtering happens inside each sink's `log()` method before the message is written.

When selecting sink types, choose the `_mt` (multi-threaded) variants for thread-safe logging across multiple threads, or `_st` (single-threaded) for better performance in single-threaded contexts.

## Complete Working Example

The following example demonstrates a production-ready setup combining `stdout_color_sink_mt` and `basic_file_sink_mt` via `dist_sink`, as shown in the official [`example/example.cpp`](https://github.com/gabime/spdlog/blob/main/example/example.cpp):

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

int main() {
    // 1. Create concrete sinks
    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");

    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");

    // 2. Combine using dist_sink
    auto dist_sink = std::make_shared<spdlog::sinks::dist_sink_mt>();
    dist_sink->add_sink(console_sink);
    dist_sink->add_sink(file_sink);

    // 3. Build and register logger
    auto logger = std::make_shared<spdlog::logger>("multi_sink", dist_sink);
    logger->set_level(spdlog::level::debug);
    spdlog::register_logger(logger);

    // 4. Log messages
    logger->info("Application started");        // Appears in console and file
    logger->debug("Debug details here");        // Appears only in file
    logger->error("Critical system error!");    // Appears in both
    
    return 0;
}

```

## Summary

- The `spdlog::logger` class in [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h) stores a `std::vector` of sink pointers and dispatches each log message to every sink in the collection.
- Use **`spdlog::sinks::dist_sink`** ([`include/spdlog/sinks/dist_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/dist_sink.h)) to aggregate multiple sinks into a single object that can be modified at runtime.
- Pass sinks directly to the `logger` constructor using iterator ranges (`begin()`, `end()`) to avoid the `dist_sink` overhead.
- Each sink maintains its own log level and pattern, allowing fine-grained control over what appears in each destination.
- Concrete sink implementations like `stdout_color_sink_mt` and `basic_file_sink_mt` live in `include/spdlog/sinks/`.

## Frequently Asked Questions

### What is the difference between using `dist_sink` and passing a vector of sinks directly?

`dist_sink` is itself a sink that forwards messages to an internal collection of child sinks. It allows you to add or remove sinks at runtime using `add_sink()` and `remove_sink()`. Passing a vector directly to the `spdlog::logger` constructor stores the sinks in the logger's internal vector without an intermediary, which is slightly more efficient but requires reconstructing the logger to change the sink set.

### Can each sink have different log levels when attached to the same logger?

Yes. Each sink has its own `set_level()` method that filters messages independently. For example, you can configure a console sink to show only `warn` and above while a file sink records `debug` and above. The logger's global level acts as a gatekeeper, but individual sinks can apply stricter filters.

### How do I add or remove sinks after the logger is created?

If you used `dist_sink`, call `dist_sink->add_sink(new_sink)` or `dist_sink->remove_sink(existing_sink)` at any time. If you passed a vector directly to the logger constructor, you must access the logger's internal sink vector (via `logger->sinks()`) and modify it, or recreate the logger with a new sink collection.

### Are spdlog sinks thread-safe?

Thread safety depends on the suffix. Sinks ending with `_mt` (multi-threaded) use mutexes to protect concurrent access and are safe to share across threads. Sinks ending with `_st` (single-threaded) offer better performance but require external synchronization if used from multiple threads. Always use `_mt` variants for multi-sink loggers in concurrent applications.