Integrating spdlog with Qt using qt_sinks: Complete Implementation Guide

Use spdlog::qt_logger_mt() or spdlog::qt_color_logger_mt() from <spdlog/sinks/qt_sinks.h> to forward log messages directly to Qt widgets via QMetaObject::invokeMethod, ensuring thread-safe UI updates even when logging from background threads.

Integrating spdlog with Qt using qt_sinks bridges high-performance C++ logging with native Qt GUI applications. The gabime/spdlog repository provides specialized sinks that leverage Qt's meta-object system to marshal log calls across thread boundaries, eliminating the need for custom queue management when updating QTextEdit or QPlainTextEdit widgets from worker threads.

How qt_sinks Work Internally

The Qt sinks derive from spdlog::sinks::base_sink<Mutex>, inheriting the same thread-safety policies as standard spdlog sinks. Two primary implementations exist:

  • qt_sink – Forwards plain text to any QObject implementing a compatible Qt slot (e.g., QPlainTextEdit::append).
  • qt_color_sink – Writes color-coded text to QTextEdit, supporting per-level color customization via QTextCharFormat.

Both sinks use QMetaObject::invokeMethod with Qt::AutoConnection to ensure log messages execute on the thread owning the target widget. This automatic marshaling prevents race conditions when background threads generate log events that must update the main GUI thread.

Step-by-Step Implementation

1. Include the Qt Sinks Header

Add the Qt-specific sink definitions to your compilation unit:

#include <spdlog/sinks/qt_sinks.h>

2. Create a Logger Using Factory Functions

Spdlog provides convenience factory functions that instantiate loggers pre-configured with Qt sinks. Choose between multi-threaded (*_mt) or single-threaded (*_st) variants based on your thread safety requirements:

// Multi-threaded logger feeding a QPlainTextEdit
auto logger = spdlog::qt_logger_mt(
    "gui_logger", 
    plainTextEdit, 
    "append(const QString&)"
);

// Single-threaded variant for single-threaded contexts
auto logger_st = spdlog::qt_logger_st(
    "gui_logger_st", 
    textEdit, 
    "append(const QString&)"
);

The meta_method parameter must specify the fully-qualified Qt slot signature, such as "append(const QString&)" for standard text editing widgets.

3. Configure Color Output (qt_color_sink only)

For colored output, use qt_color_logger_mt or instantiate qt_color_sink directly. Customize appearance per log level using set_color():

auto color_logger = spdlog::qt_color_logger_mt(
    "color_logger", 
    textEdit, 
    1000  // max_buffer_size
);

// Customize warning level color
QTextCharFormat warn_format;
warn_format.setForeground(Qt::darkYellow);
color_logger->set_color(spdlog::level::warn, warn_format);

Complete Code Examples

Plain Text Logging to QPlainTextEdit

This example creates a multi-threaded logger that appends plain text to a QPlainTextEdit widget:

QPlainTextEdit *log_view = new QPlainTextEdit(parent);
auto logger = spdlog::qt_logger_mt(
    "qt_logger", 
    log_view, 
    "append(const QString&)"
);

logger->set_level(spdlog::level::debug);
logger->info("Application started successfully");

Color-Coded Logging to QTextEdit

This implementation uses qt_color_sink to display warnings in yellow and errors in red:

QTextEdit *color_log_view = new QTextEdit(parent);
auto color_logger = spdlog::qt_color_logger_mt(
    "qt_color_logger", 
    color_log_view, 
    1000
);

// Configure warning appearance
QTextCharFormat warn_fmt;
warn_fmt.setForeground(Qt::darkYellow);
color_logger->set_color(spdlog::level::warn, warn_fmt);

color_logger->warn("Disk space running low");

Lifetime Management and Thread Safety

Because qt_sink stores a raw QObject* pointer without maintaining a hard reference, you must ensure the target widget outlives the logger. According to the implementation in include/spdlog/sinks/qt_sinks.h, the constructor guards against null objects:

if (!qt_object_) {
    throw_spdlog_ex("qt_sink: qt_object is null");
}

If the widget is destroyed while the logger remains active, subsequent log calls may crash or throw spdlog_ex exceptions. Best practices include:

  • Tie the logger's lifetime to the widget's lifetime using std::shared_ptr or parent-child relationships
  • Explicitly call logger->flush() and reset the logger (logger.reset()) before destroying the Qt widget
  • Use QPointer to monitor widget validity if implementing custom sink wrappers

Summary

  • qt_sink and qt_color_sink in include/spdlog/sinks/qt_sinks.h provide native Qt integration for spdlog
  • Factory functions qt_logger_mt() and qt_logger_st() simplify instantiation with proper thread-safety guarantees
  • Internal implementation uses QMetaObject::invokeMethod with Qt::AutoConnection for automatic thread marshaling
  • The meta_method parameter requires exact Qt slot signatures like "append(const QString&)"
  • Widget lifetime must exceed logger lifetime to avoid spdlog_ex exceptions or dangling pointers
  • Color customization via set_color() accepts QTextCharFormat objects for rich text styling

Frequently Asked Questions

How do I safely log to the Qt GUI from a background thread?

The qt_sinks automatically handle cross-thread communication using QMetaObject::invokeMethod with Qt::AutoConnection. When you log from a background thread, the sink marshals the message to the main GUI thread where the widget update executes safely. Use the *_mt factory functions if multiple threads will call the logger simultaneously.

Which Qt widgets work with qt_sinks?

Any QObject exposing a slot that accepts const QString& (or compatible parameter) works with qt_sink. Common choices include QPlainTextEdit (using "append(const QString&)"), QTextEdit, or custom QObject subclasses with user-defined slots. The qt_color_sink specifically requires QTextEdit to support color formatting via QTextCharFormat.

How do I change text colors for different log levels?

Call set_color(spdlog::level::level_enum, QTextCharFormat) on a qt_color_sink instance. Create a QTextCharFormat object, set its foreground color using setForeground(), and pass it to the sink. This affects only future log messages; previously displayed text retains its original formatting.

What happens if my Qt widget is destroyed before the logger?

The sink throws a spdlog_ex exception during construction if the provided QObject* is null. If the widget is destroyed after logger creation, subsequent log calls may dereference invalid memory. Always ensure the logger is destroyed or reset before the target widget, or implement a wrapper that checks QPointer validity before invoking the sink.

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 →