# How to Configure spdlog to Route Different Log Levels to Different Sinks

> Configure spdlog to route log levels to different sinks by attaching sinks to a logger and setting min levels. Learn how to independently filter messages by severity.

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

---

**To route different log levels to different outputs in spdlog, attach multiple sinks to a single logger and set individual minimum log levels on each sink using `set_level()`, allowing each sink to filter messages independently based on severity.**

The **gabime/spdlog** library implements a flexible sink-based architecture where log routing is controlled at the sink level rather than the logger level. Each sink acts as an independent output channel that can be configured to accept or reject messages based on their severity, enabling sophisticated log distribution strategies without complex custom filters.

## How Sink-Level Filtering Works

spdlog routes messages through a two-stage filtering process. First, the **logger** checks its own level; if the message passes, it forwards the `log_msg` to every attached **sink**. Each sink then performs its own level check via the `should_log()` method implemented in [[`base_sink.h`](https://github.com/gabime/spdlog/blob/main/base_sink.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/base_sink.h).

The key components involved are:

- **`spdlog::sinks::sink`** – The abstract interface defining `set_level()` and `log()` methods ([[`sink.h`](https://github.com/gabime/spdlog/blob/main/sink.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/sink.h))
- **`spdlog::sinks::base_sink`** – CRTP base class that implements level filtering logic ([[`base_sink.h`](https://github.com/gabime/spdlog/blob/main/base_sink.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/base_sink.h))
- **`spdlog::logger`** – Container that holds a vector of sinks and broadcasts messages to all of them ([[`spdlog.h`](https://github.com/gabime/spdlog/blob/main/spdlog.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/spdlog.h))

Because filtering occurs inside each sink's `sink_it_()` implementation, you can attach sinks with non-overlapping level ranges to achieve strict routing separation.

## Configuring Per-Sink Log Levels

### Instantiate Individual Sinks

Create separate sink instances for each output destination. Common options include `stdout_color_sink_mt` for console output and `basic_file_sink_mt` for file logging.

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

auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto error_file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("errors.log");
auto trace_file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("trace.log");

```

### Assign Minimum Log Levels

Call `set_level()` on each sink to establish its acceptance threshold. Messages below this level are silently discarded by that specific sink.

```cpp
console_sink->set_level(spdlog::level::info);      // Accepts INFO and above
error_file_sink->set_level(spdlog::level::err);    // Accepts only ERROR and CRITICAL
trace_file_sink->set_level(spdlog::level::trace);  // Accepts everything

```

### Assemble the Multi-Sink Logger

Pass the configured sinks to a `spdlog::logger` constructor using `sinks_init_list` or a vector. The logger will broadcast every message to all sinks, but each sink filters independently.

```cpp
auto logger = std::make_shared<spdlog::logger>("multi_sink_logger",
    spdlog::sinks_init_list{console_sink, error_file_sink, trace_file_sink});
spdlog::register_logger(logger);

```

## Using dist_sink for Sink Groups

For complex configurations, wrap multiple sinks in a **`spdlog::sinks::dist_sink`** ([[`dist_sink.h`](https://github.com/gabime/spdlog/blob/main/dist_sink.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/dist_sink.h)). This distributor sink forwards messages to its child sinks, allowing you to treat a group of outputs as a single unit.

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

auto dist_sink = std::make_shared<spdlog::sinks::dist_sink_mt>();
dist_sink->add_sink(console_sink);
dist_sink->add_sink(error_file_sink);

auto logger = std::make_shared<spdlog::logger>("distributed", dist_sink);

```

## Complete Working Example

The following example demonstrates routing **TRACE**-**DEBUG** to a debug file, **INFO**-**WARN** to the console, and **ERROR**-**CRITICAL** to both the console and a separate error file.

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

int main() {
    // Console: INFO and above (colorized)
    auto console = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    console->set_level(spdlog::level::info);
    
    // Error file: ERROR and CRITICAL only
    auto error_file = std::make_shared<spdlog::sinks::basic_file_sink_mt>("errors.log", true);
    error_file->set_level(spdlog::level::err);
    
    // Debug file: TRACE and DEBUG only
    auto debug_file = std::make_shared<spdlog::sinks::basic_file_sink_mt>("debug.log", true);
    debug_file->set_level(spdlog::level::trace);
    // Filter out INFO and above from debug file using a custom filter or null sink for higher levels
    // Note: spdlog doesn't support max_level filter directly, so we use a trick:
    // For exclusive routing, you'd need a custom sink or check message level in formatter
    
    // Create logger with all three sinks
    auto logger = std::make_shared<spdlog::logger>("router",
        spdlog::sinks_init_list{console, error_file, debug_file});
    
    // Set logger level to lowest (trace) to ensure all messages reach the sinks
    logger->set_level(spdlog::level::trace);
    spdlog::set_default_logger(logger);
    
    // Test routing
    logger->trace("Trace message -> debug.log only");
    logger->debug("Debug message -> debug.log only");
    logger->info("Info message -> console only");
    logger->error("Error message -> console and errors.log");
    logger->critical("Critical message -> console and errors.log");
    
    logger->flush();
    return 0;
}

```

**Note:** The example above shows that `debug_file` will receive TRACE and DEBUG, but also ERROR and CRITICAL because those levels are higher than TRACE. For truly exclusive routing (e.g., only TRACE-DEBUG to the debug file), you would need to implement a custom sink or use a filter functor, as spdlog's `set_level()` only establishes a minimum threshold.

## Key Source Files

| File | Purpose |
|------|---------|
| [[`include/spdlog/spdlog.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/spdlog.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/spdlog.h) | Logger creation and global registry |
| [[`include/spdlog/sinks/sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/sink.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/sink.h) | Abstract base class defining the sink interface |
| [[`include/spdlog/sinks/base_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/base_sink.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/base_sink.h) | CRTP implementation with `should_log()` level checking |
| [[`include/spdlog/sinks/dist_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/dist_sink.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/dist_sink.h) | Distributor sink for grouping child sinks |
| [[`include/spdlog/sinks/stdout_color_sinks.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/stdout_color_sinks.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/stdout_color_sinks.h) | Console output with ANSI color support |
| [[`include/spdlog/sinks/basic_file_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/basic_file_sink.h)](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/basic_file_sink.h) | Simple file logging implementation |

## Summary

- **Sink-level filtering** is the idiomatic spdlog approach to route different log levels to different outputs.
- Use **`set_level()`** on individual sinks to establish minimum severity thresholds.
- Attach multiple sinks to a single **`spdlog::logger`** via `sinks_init_list` or vectors.
- The filtering logic resides in **[`base_sink.h`](https://github.com/gabime/spdlog/blob/main/base_sink.h)**, where each sink independently decides whether to process a message.
- For complex topologies, use **`dist_sink`** to manage groups of sinks as a single unit.

## Frequently Asked Questions

### Can I route only a specific log level to a sink (exclusive routing)?

**No, not with `set_level()` alone**, since it only filters out messages below a minimum threshold. To achieve exclusive routing (e.g., only WARNING messages), you must implement a custom sink inheriting from **`spdlog::sinks::base_sink`** and override `sink_it_()` to check for exact level matches before writing. Alternatively, use a custom formatter or filter predicate if using spdlog v1.4+ filter support.

### What happens if I set different levels on the logger and the sinks?

The logger acts as a first-line gate. If a message is below the **logger's** level, it is discarded immediately and never reaches any sinks. If it passes the logger's filter, it is forwarded to all sinks, which then apply their individual level filters. Therefore, set the logger to the lowest level (`trace`) when using per-sink filtering to ensure all routing decisions happen at the sink level.

### Does spdlog support dynamic level changes at runtime?

**Yes.** Both `logger->set_level()` and `sink->set_level()` are thread-safe operations that can be called at any time. Changes take effect immediately for all subsequent log calls without requiring restarts or recompilation.

### How do I prevent duplicate log messages when using multiple sinks?

**Duplicate messages are the expected behavior** when multiple sinks accept the same log level. If you want messages to appear in only one destination based on level, ensure your sink level ranges do not overlap. For example, set one sink to `info` (which accepts INFO and above) and another to `debug` but with a custom filter that rejects INFO and higher, or use distinct loggers rather than multiple sinks on the same logger.