# How to Use spdlog Callback Sink for Custom Log Notifications

> Learn to use spdlog callback sink for custom log notifications. Intercept logs with std::function for real-time alerts, UI updates, or external integrations.

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

---

**Use `spdlog::sinks::callback_sink` to intercept log messages with a custom `std::function` handler, enabling real-time notifications, UI updates, or external service integration while maintaining standard logging outputs.**

The **callback sink** in the `gabime/spdlog` repository provides a mechanism to execute arbitrary code for every log message dispatched through a logger. Unlike file or console sinks that write to I/O streams, the callback sink forwards structured log data to user-defined callables, making it ideal for integrating spdlog with application-specific notification systems, monitoring dashboards, or graphical interfaces.

## What Is the spdlog Callback Sink?

The callback sink is a template-based sink implementation located in [`include/spdlog/sinks/callback_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/callback_sink.h). It inherits from `spdlog::sinks::base_sink` and accepts a callable object—typically a lambda or function pointer—that receives each `spdlog::details::log_msg` instance before the message is destroyed.

This architecture allows your application to:
- Extract fully formatted message strings for UI rendering
- Push log events to external APIs or message queues
- Trigger alerts based on log level thresholds
- Maintain audit trails in custom data structures

The sink does not perform any I/O operations itself; it merely acts as a bridge between spdlog's formatting engine and your application logic.

## Key Implementation Details

According to the source code in [`callback_sink.h`](https://github.com/gabime/spdlog/blob/main/callback_sink.h), the implementation provides two primary type aliases:

- **`callback_sink_mt`** – Uses `std::mutex` for thread-safe operation across multiple logging threads
- **`callback_sink_st`** – Uses `spdlog::details::null_mutex` for single-threaded contexts where synchronization overhead is unnecessary

Both variants accept a callback function matching the signature:

```cpp
std::function<void(const spdlog::details::log_msg& msg)>

```

The `log_msg` structure contains:
- `msg.payload` – The formatted message string as `fmt::memory_buffer`
- `msg.logger_name` – The identifier of the originating logger
- `msg.level` – The severity level (debug, info, warn, error, etc.)
- `msg.time` – The timestamp of the log event
- `msg.thread_id` – The ID of the thread that generated the log

Factory helpers `spdlog::factory::create_callback_sink_mt` and `create_callback_sink_st` provide convenient construction methods that return ready-to-use logger instances.

## How to Create and Configure a Callback Sink

### Basic Single-Threaded Setup

For single-threaded applications or when logging occurs from one thread only, use `callback_sink_st` to avoid mutex overhead:

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/sinks/callback_sink.h>
#include <iostream>

int main() {
    auto custom_handler = [](const spdlog::details::log_msg& msg) {
        std::string formatted = fmt::to_string(msg.payload);
        std::cout << "[NOTIFICATION] " << formatted << std::endl;
    };
    
    auto cb_sink = std::make_shared<spdlog::sinks::callback_sink_st>(custom_handler);
    spdlog::logger logger("my_app", cb_sink);
    
    logger.info("Application initialized");
    logger.error("Connection failed: {}", "timeout");
}

```

### Multi-Threaded Configuration

When logging from multiple threads, instantiate `callback_sink_mt` to ensure thread-safe callback invocation:

```cpp
#include <spdlog/sinks/callback_sink.h>
#include <thread>
#include <vector>

void setup_thread_safe_logging() {
    auto handler = [](const spdlog::details::log_msg& msg) {
        // Safe to access msg data; mutex held during callback execution
        auto level_str = spdlog::level::to_string_view(msg.level);
        process_notification(level_str, fmt::to_string(msg.payload));
    };
    
    auto sink = std::make_shared<spdlog::sinks::callback_sink_mt>(handler);
    auto logger = std::make_shared<spdlog::logger>("mt_logger", sink);
    spdlog::set_default_logger(logger);
}

```

### Using Factory Helpers

The factory functions streamline creation by returning configured logger instances:

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

void use_factory_method() {
    auto callback = [](const spdlog::details::log_msg& msg) {
        send_to_monitoring_service(fmt::to_string(msg.payload));
    };
    
    auto logger = spdlog::factory::create_callback_sink_mt("monitor", callback);
    logger->warn("Disk usage exceeded 90%");
}

```

## Practical Examples

### Console and Callback Combination

Combine the callback sink with standard output sinks to preserve console logging while triggering notifications:

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

int main() {
    auto notification_callback = [](const spdlog::details::log_msg& msg) {
        if (msg.level >= spdlog::level::err) {
            alert_admin(fmt::to_string(msg.payload));
        }
    };
    
    auto console = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    auto callback = std::make_shared<spdlog::sinks::callback_sink_mt>(notification_callback);
    
    spdlog::logger logger("dual_logger", {console, callback});
    logger.set_level(spdlog::level::debug);
    
    logger.info("System startup complete");
    logger.error("Database connection lost");  // Triggers callback alert
}

```

### GUI Integration Pattern

Integrate with Qt or other UI frameworks by ensuring the callback executes on the main thread:

```cpp
#include <spdlog/sinks/callback_sink.h>
#include <QPlainTextEdit>
#include <QMetaObject>

void attach_to_qt_widget(QPlainTextEdit* log_view) {
    auto gui_callback = [log_view](const spdlog::details::log_msg& msg) {
        QString text = QString::fromStdString(fmt::to_string(msg.payload));
        
        // Ensure UI updates occur on the main thread
        QMetaObject::invokeMethod(log_view, [log_view, text]() {
            log_view->appendPlainText(text);
        });
    };
    
    auto sink = std::make_shared<spdlog::sinks::callback_sink_mt>(gui_callback);
    spdlog::logger logger("ui_logger", sink);
    spdlog::set_default_logger(std::make_shared<spdlog::logger>(logger));
}

```

### HTTP/Webhook Notifications

Forward critical logs to external monitoring services:

```cpp
#include <spdlog/sinks/callback_sink.h>
#include <cpr/cpr.h>  // Example HTTP library

void setup_http_notifications() {
    auto webhook_callback = [](const spdlog::details::log_msg& msg) {
        std::string json_payload = fmt::format(
            R"({{"timestamp":"{}","level":"{}","message":"{}"}})",
            msg.time.time_since_epoch().count(),
            spdlog::level::to_string_view(msg.level),
            fmt::to_string(msg.payload)
        );
        
        cpr::PostAsync(
            cpr::Url{"https://alerts.company.com/webhook"},
            cpr::Body{json_payload},
            cpr::Header{{"Content-Type", "application/json"}}
        );
    };
    
    auto sink = std::make_shared<spdlog::sinks::callback_sink_mt>(webhook_callback);
    auto logger = std::make_shared<spdlog::logger>("http_logger", sink);
    spdlog::register_logger(logger);
}

```

## Testing and Validation

The `gabime/spdlog` repository includes comprehensive tests in [`tests/test_custom_callbacks.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_custom_callbacks.cpp) that demonstrate expected behavior and edge cases. Review this file to understand:
- How the sink handles different log levels
- Thread safety verification patterns
- Message formatting guarantees

The example application in [`example/example.cpp`](https://github.com/gabime/spdlog/blob/main/example/example.cpp) also contains practical demonstrations of callback sink usage within complete programs.

## Summary

- **Callback sinks** provide a `std::function` interface for custom log handling without modifying spdlog's core
- Choose **`callback_sink_mt`** for multi-threaded environments and **`callback_sink_st`** for single-threaded contexts
- The callback receives a `spdlog::details::log_msg` reference containing formatted payload, timestamps, and metadata
- Combine callback sinks with file or console sinks to maintain traditional logging while adding custom notifications
- Source implementation resides in [`include/spdlog/sinks/callback_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/callback_sink.h) with tests available in [`tests/test_custom_callbacks.cpp`](https://github.com/gabime/spdlog/blob/main/tests/test_custom_callbacks.cpp)

## Frequently Asked Questions

### What is the signature of the callback function for spdlog callback sinks?

The callback must match `std::function<void(const spdlog::details::log_msg& msg)>`. The `log_msg` parameter provides access to `msg.payload` (the formatted message), `msg.level` (severity), `msg.time` (timestamp), and other metadata fields.

### Is the spdlog callback sink thread-safe?

Yes, when using `callback_sink_mt`, which locks a `std::mutex` before invoking your callback. For single-threaded applications, use `callback_sink_st` to avoid synchronization overhead. The thread-safe variant ensures that your callback executes atomically relative to other logging operations.

### Can I combine callback sinks with other spdlog sinks?

Absolutely. The callback sink functions like any other spdlog sink and can be passed alongside `stdout_color_sink_mt`, `basic_file_sink_mt`, or rotating file sinks to a logger constructor. This allows simultaneous file logging, console output, and custom notification handling.

### How do I access the formatted message string in the callback?

Convert `msg.payload`—which is an `fmt::memory_buffer`—to a `std::string` using `fmt::to_string(msg.payload)`. This yields the fully formatted log message including any pattern prefixes (timestamps, logger names) that the sink's formatter has applied.