How to Use spdlog Callback Sink for Custom Log Notifications

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. 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, 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:

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:

#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:

#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:

#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:

#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:

#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:

#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 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 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 with tests available in 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →