# How to Create a Multi‑Sink Logger with Different Levels and Formats in spdlog

> Learn to create a multi-sink logger in spdlog with unique levels and formats for each sink. Gain precise control over log message output and customization.

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

---

**A `spdlog::logger` aggregates multiple sink objects, each with independent `set_level()` and `set_pattern()` configurations, enabling fine‑grained control over where and how log messages are emitted.**

The spdlog C++ logging library (maintained at `gabime/spdlog`) supports composing loggers from arbitrary collections of **sinks**—independent output destinations that each maintain their own filtering and formatting rules. This architecture allows you to route warnings to a concise console output while simultaneously capturing full trace‑level detail to persistent storage.

## Understanding the Multi‑Sink Architecture

The spdlog source implements multi‑sink loggers through three cooperating components:

1. **Sink objects** – Concrete classes derived from `spdlog::sinks::sink` in `include/spdlog/sinks/`
2. **Distribution mechanism** – The `spdlog::sinks::dist_sink` class transparently forwards records to multiple child sinks
3. **Logger object** – `spdlog::logger` defined in [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h) aggregates sinks and applies a global level filter

The log flow follows this path:

```cpp
log call → logger (global level) → for each sink:
               sink (sink level) → format (sink pattern) → output

```

Because each sink performs its own level check and formatting, you achieve complete orthogonality between destinations. The global logger level provides an additional filtering layer that works in conjunction with—not instead of—per‑sink levels.

## Source Files and Key Classes

| File | Purpose |
|------|---------|
| [`include/spdlog/sinks/stdout_color_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/stdout_color_sink.h) | Console sink 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) | Simple file sink with append/truncate options |
| [`include/spdlog/sinks/dist_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/dist_sink.h) | Internal distributor for multi‑sink loggers |
| [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h) | Logger class definition and sink aggregation |

## Complete Working Example

The following example, adapted from [`example/example.cpp`](https://github.com/gabime/spdlog/blob/main/example/example.cpp) in the spdlog repository, demonstrates a **multi‑sink logger with different levels and formats**—a colored console sink filtering to warnings and above, plus a file sink capturing all messages from trace upward:

```cpp
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sink.h"
#include "spdlog/sinks/basic_file_sink.h"

int main()
{
    // ---- Create console sink: warnings and above, colored concise format ----
    auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    console_sink->set_level(spdlog::level::warn);
    console_sink->set_pattern("[%^%l%$] %v");

    // ---- Create file sink: all messages, detailed timestamp format ----
    auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(
        "logs/multisink.txt", true /* truncate */);
    file_sink->set_level(spdlog::level::trace);
    file_sink->set_pattern("[%Y-%m-%d %H:%M:%S] [%l] %v");

    // ---- Construct logger with sink vector; set global level ----
    spdlog::logger multi_logger("multi_sink", {console_sink, file_sink});
    multi_logger.set_level(spdlog::level::debug);

    // ---- Emit test messages ----
    multi_logger.warn("This appears in console AND file");
    multi_logger.info("This appears only in file");
    multi_logger.debug("Debug detail - file only");
    multi_logger.trace("Trace data - file only (below global level)");

    multi_logger.flush();
    return 0;
}

```

## Breaking Down the Configuration

### Per‑Sink Level Filtering

Each sink implements `void set_level(level::level_enum lvl)` as declared in the base `sink` class. In the example:

- `console_sink->set_level(spdlog::level::warn)` — discards `info`, `debug`, and `trace`
- `file_sink->set_level(spdlog::level::trace)` — accepts all severity levels

The logger's global level (`multi_logger.set_level(spdlog::level::debug)`) adds a second filter: `trace` calls never reach either sink because they fail the global check, despite the file sink's permissive setting.

### Per‑Sink Pattern Formatting

Each sink implements `void set_pattern(const std::string& pattern)`:

| Pattern | Effect |
|---------|--------|
| `[%^%l%$] %v` | Colored level (`%^`…`%$` enables ANSI color), raw message |
| `[%Y-%m-%d %H:%M:%S] [%l] %v` | Full timestamp, level, message |

Patterns are evaluated independently per sink, allowing completely different output styles for each destination.

### Thread Safety Considerations

The `*_mt` suffix (e.g., `stdout_color_sink_mt`, `basic_file_sink_mt`) selects **multi‑threaded** sinks that use internal locking. For single‑threaded applications, the `*_st` variants eliminate synchronization overhead. Mixing `*_mt` and `*_st` sinks within one logger is valid—the logger itself does not impose additional locking beyond what each sink provides.

## Dynamic Runtime Reconfiguration

One advantage of spdlog's multi‑sink design is the ability to modify behavior without recreating objects. Both level and pattern can be changed at any time:

```cpp
// Temporarily suppress console output
console_sink->set_level(spdlog::level::err);

// Switch file to more verbose format for debugging session
file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%s:%#] [%!] [%l] %v");

// Restore previous settings
console_sink->set_level(spdlog::level::warn);

```

Changes take effect immediately for all subsequent log calls.

## Extending to Additional Sinks

The same vector-based construction supports any spdlog sink type. Common additions include:

- `spdlog::sinks::rotating_file_sink_mt` — size‑limited rotating files
- `spdlog::sinks::daily_file_sink_mt` — date‑based rotation
- `spdlog::sinks::syslog_sink` — POSIX syslog integration
- `spdlog::sinks::udp_sink` — network forwarding
- `spdlog::sinks::null_sink_mt` — discard sink for benchmarks

Simply add more `std::shared_ptr<spdlog::sinks::sink>` objects to the initializer list passed to the `spdlog::logger` constructor.

## Summary

- **Sink objects** are independent output destinations with individual `set_level()` and `set_pattern()` controls
- **`spdlog::logger`** aggregates sinks through `dist_sink` internal distribution; global level adds filtering layer
- **Constructor interface**: `spdlog::logger(name, {sink1, sink2, ...})` creates multi‑sink loggers directly
- **Thread variants**: Use `*_mt` for concurrent access, `*_st` for single‑threaded performance
- **Runtime flexibility**: Levels and patterns can be modified without logger reconstruction

## Frequently Asked Questions

### How do I add or remove sinks from an existing logger?

Sinks cannot be added to an existing `spdlog::logger` after construction. Create a new logger with the desired sink collection, or maintain a `std::shared_ptr<spdlog::sinks::dist_sink>` directly—its `add_sink()` and `remove_sink()` methods enable dynamic membership changes, then pass the dist_sink to a logger wrapping it.

### Why does my trace message not appear despite the file sink being set to trace level?

The logger's global level acts as a first‑pass filter. If `logger.set_level()` is higher than `trace`, messages are discarded before reaching any sink. Both global and per‑sink levels must permit a message for it to be emitted.

### Can different sinks use different time zones for timestamps?

Yes—spdlog's pattern formatter respects the global time zone source configured via `spdlog::set_pattern()` or per‑sink pattern strings with `%z`/`%Z` specifiers. For zone‑specific sinks, implement custom `spdlog::formatter` subclasses or use `spdlog::sinks::callback_sink` with manual timestamp generation.

### What is the performance cost of multiple sinks?

Each sink executes independently, so costs are additive. The `dist_sink` implementation in [`include/spdlog/sinks/dist_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/dist_sink.h) iterates the sink vector with minimal overhead. For high‑throughput scenarios, prefer `*_st` sinks where thread safety permits, and consider `spdlog::sinks::async_sink` wrapper for I/O‑bound destinations.