# How to Create a Custom Sink for spdlog in C++

> Learn to create a custom sink for spdlog in C++ by inheriting from base_sink and implementing sink_it() and flush(). Enhance your logging capabilities with spdlog.

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

---

**To create a custom sink for spdlog, inherit from `spdlog::sinks::base_sink<Mutex>` and implement the `sink_it_()` method to process formatted log messages and `flush_()` to synchronize output buffers.**

spdlog routes every log record through one or more *sink* objects that determine where output is written. While the library ships with sinks for files, consoles, and streams, many applications require sending logs to custom destinations like databases, network services, or proprietary APIs. This guide demonstrates how to create a custom sink for spdlog using the base classes defined in the gabime/spdlog repository.

## Understanding the Sink Architecture

The spdlog sink architecture separates the *log message* from the *output destination*. Two primary components make customization straightforward:

- **`spdlog::sinks::sink`** ([include/spdlog/sinks/sink.h](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/sink.h)) defines the pure virtual interface that every sink must implement, including `log()`, `flush()`, `set_pattern()`, and `set_formatter()`.

- **`spdlog::sinks::base_sink<Mutex>`** ([include/spdlog/sinks/base_sink.h](https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/sinks/base_sink.h)) provides a ready-made implementation that handles thread locking, level filtering, and formatter management. By subclassing `base_sink`, you only need to provide two low-level hooks for actual output.

## Step-by-Step Implementation

Follow these steps to implement a functional custom sink.

### Step 1: Choose a Mutex Type for Thread Safety

The `base_sink` template accepts a mutex type that determines thread safety:

- **`std::mutex`** – Use for thread-safe sinks accessed by multiple threads concurrently.
- **`spdlog::details::null_mutex`** – Use for single-threaded contexts to eliminate locking overhead.

### Step 2: Inherit from base_sink<Mutex>

Create your class inheriting from `base_sink` with your chosen mutex:

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

class my_custom_sink : public spdlog::sinks::base_sink<std::mutex> {
    // Implementation details...
};

```

### Step 3: Implement sink_it_() and flush_()

Override these two pure virtual methods defined in [`base_sink.h`](https://github.com/gabime/spdlog/blob/main/base_sink.h):

- **`void sink_it_(const spdlog::details::log_msg& msg)`** – Called for every log record. Use the inherited `formatter_` member to convert `msg` into a string, then write it to your destination.
- **`void flush_()`** – Called when the logger is flushed. Perform any necessary OS-level synchronization (e.g., `fflush`, `fsync`).

The `base_sink` automatically locks the mutex, checks log levels, and calls the formatter before invoking `sink_it_()`, so your implementation focuses solely on output logic.

### Step 4: Add Configuration Helpers (Optional)

For sinks requiring runtime configuration (timeouts, connection strings, etc.), expose public methods to set these options. The repository's test sink ([tests/test_sink.h](https://github.com/gabime/spdlog/blob/v1.x/tests/test_sink.h)) demonstrates this pattern with methods like `set_delay()` and counters for testing.

## Complete Custom Sink Example

The following example implements a single-threaded file appender using `null_mutex`. It formats messages using the sink's inherited `formatter_` and writes them to an `std::ofstream`.

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/sinks/base_sink.h>
#include <spdlog/details/null_mutex.h>
#include <fstream>

class file_append_sink : public spdlog::sinks::base_sink<spdlog::details::null_mutex>
{
public:
    explicit file_append_sink(const std::string& path) : file_(path, std::ios::app) {}

protected:
    void sink_it_(const spdlog::details::log_msg& msg) override
    {
        // Format the message using the sink's formatter_
        spdlog::memory_buf_t formatted;
        formatter_->format(msg, formatted);
        file_ << fmt::to_string(formatted);
    }

    void flush_() override { file_.flush(); }

private:
    std::ofstream file_;
};

```

## Integrating the Custom Sink

After defining your sink class, instantiate it and attach it to a logger:

```cpp
int main()
{
    auto sink = std::make_shared<file_append_sink>("mylog.txt");
    auto logger = std::make_shared<spdlog::logger>("custom", sink);
    spdlog::register_logger(logger);
    
    logger->info("Hello from a custom sink!");
}

```

To use multiple destinations, push several sinks onto the logger's internal vector using `logger->sinks().push_back(...)`.

## Summary

- Derive from **`spdlog::sinks::base_sink<Mutex>`** to create a custom sink for spdlog, selecting `std::mutex` for thread safety or `null_mutex` for single-threaded use.
- Implement **`sink_it_()`** to handle formatted output using the inherited **`formatter_`** member.
- Implement **`flush_()`** to ensure data reaches its destination.
- Reference the abstract interface in [`include/spdlog/sinks/sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/sink.h) and the helper implementation in [`include/spdlog/sinks/base_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/base_sink.h).
- Study [`tests/test_sink.h`](https://github.com/gabime/spdlog/blob/main/tests/test_sink.h) for advanced patterns like state management and delayed logging.

## Frequently Asked Questions

### Do I need to implement my own locking mechanism?

No. When you inherit from `spdlog::sinks::base_sink<Mutex>`, the base class handles all locking before calling your `sink_it_()` implementation. If you inherit directly from `spdlog::sinks::sink` instead, you must implement `log()` yourself and manage thread safety manually.

### What is the difference between `sink_it_()` and the public `log()` method?

The public `log()` method is the entry point defined in the `sink` interface that handles level filtering and thread synchronization. `sink_it_()` is the private hook called by `base_sink` after these concerns are addressed, allowing you to focus only on outputting the already-formatted message.

### Can I use a custom sink in a multi-threaded application?

Yes. Instantiate your custom sink with `std::mutex` as the template parameter: `class my_sink : public spdlog::sinks::base_sink<std::mutex>`. This ensures thread-safe access to the `sink_it_()` and `flush_()` methods.

### How do I format log messages within my custom sink?

Use the inherited **`formatter_`** member variable. Call `formatter_->format(msg, buffer)` where `msg` is the `log_msg` parameter and `buffer` is a `spdlog::memory_buf_t`. This respects any pattern set via `set_pattern()` or `set_formatter()` on the sink.