How to Use spdlog with Qt Widgets for GUI Logging

Use spdlog's qt_sink or qt_color_sink classes from include/spdlog/sinks/qt_sinks.h to forward log messages directly to QTextEdit or QPlainTextEdit widgets via Qt's meta-object system, ensuring thread-safe GUI updates always occur on the main thread.

The gabime/spdlog library provides dedicated Qt integration sinks that bridge high-performance C++ logging with graphical user interfaces. When you use spdlog with Qt widgets, log messages are formatted and dispatched through QMetaObject::invokeMethod, guaranteeing that GUI updates happen safely within Qt's event loop regardless of which thread generated the log entry.

Qt Sink Types and Thread Safety

spdlog implements two specialized sinks for Qt applications in include/spdlog/sinks/qt_sinks.h:

  • qt_sink – Targets QTextEdit, QPlainTextEdit, or any QObject with a compatible slot. It forwards formatted text via the default append or appendPlainText slots.
  • qt_color_sink – Extends qt_sink with colorized output support for QTextEdit only, applying different QTextCharFormat styles based on log levels and optionally truncating old lines to maintain performance.

Both sinks inherit from spdlog::sinks::base_sink<Mutex>, making them compatible with spdlog's entire formatter ecosystem. The template parameter Mutex determines thread safety: use std::mutex for multi-threaded applications or spdlog::details::null_mutex for single-threaded contexts where you want to avoid locking overhead.

Implementation Details

Thread Safety and Mutex Selection

The sinks use the Mutex template parameter to protect internal state. When you create a logger via qt_logger_mt, the sink instantiates with std::mutex, allowing concurrent logging from multiple threads. For GUI applications where all logging occurs on the main thread, qt_logger_st uses null_mutex for zero-overhead synchronization.

Message Delivery via QMetaObject

In qt_sink::sink_it_, the sink first applies the formatter to convert the log message into a QString. It then uses QMetaObject::invokeMethod with Qt::AutoConnection to emit the string to the widget's slot specified by meta_method_. This mechanism ensures that even if a background thread calls spdlog::info(), the actual GUI update executes on the main thread, preventing race conditions and UI corruption.

Color Handling and Line Limits

The qt_color_sink class extracts color ranges from spdlog::details::log_msg when color formatting is enabled. It constructs payloads in either UTF-8 or Latin-1 encoding and invokes a static helper method that inserts text with the appropriate QTextCharFormat. Additionally, the sink enforces a configurable maximum line count, automatically removing the oldest QTextBlock entries when the limit is exceeded to prevent unbounded memory growth in long-running applications.

Code Examples

Basic QTextEdit Logging

The simplest integration uses qt_logger_mt to create a multi-threaded logger that appends to a QTextEdit:

#include <QApplication>
#include <QTextEdit>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/qt_sinks.h>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QTextEdit editor;
    editor.show();

    // Create multi-threaded logger targeting QTextEdit::append
    auto logger = spdlog::qt_logger_mt("qt", &editor);
    spdlog::set_default_logger(logger);
    spdlog::set_level(spdlog::level::debug);

    spdlog::info("Application started");
    spdlog::debug("Debug value: {}", 42);
    spdlog::error("Critical failure!");

    return app.exec();
}

QPlainTextEdit Integration

For plain text widgets, specify the appendPlainText slot explicitly:

QPlainTextEdit *plain = new QPlainTextEdit;
auto logger = spdlog::qt_logger_mt("plain", plain, "appendPlainText");

Colorized Logging with Line Limits

Use qt_color_logger_mt to enable syntax highlighting and prevent memory leaks in high-volume logging:

QTextEdit *colored = new QTextEdit;
int maxLines = 500;  // Retain only the most recent 500 lines

auto logger = spdlog::qt_color_logger_mt("colored", colored, maxLines, true);
// Third parameter enables UTF-8 support

logger->info("System initialized");
logger->warn("Low memory: {}%", 15);
logger->error("Connection refused");

Custom Slot Receivers

You can route logs to custom QObject slots for filtering, timestamping, or network forwarding:

class LogInterceptor : public QObject {
    Q_OBJECT
public slots:
    void handleLog(const QString &msg) {
        // Prepend timestamp or filter sensitive data
        QString enriched = QDateTime::currentDateTime().toString() + " " + msg;
        textEdit->append(enriched);
    }
};

LogInterceptor interceptor;
auto logger = spdlog::qt_logger_mt("custom", &interceptor, "handleLog");

Summary

  • Include qt_sinks.h from the spdlog repository to access Qt-specific sinks.
  • Choose the appropriate mutex type: qt_logger_mt for thread-safe logging or qt_logger_st for single-threaded performance.
  • Use qt_sink for QTextEdit and QPlainTextEdit, or qt_color_sink for colorized output with automatic line truncation.
  • Leverage QMetaObject::invokeMethod implicitly through these sinks to guarantee main-thread GUI updates from any thread context.
  • Specify custom slots when you need to preprocess messages before displaying them in the widget.

Frequently Asked Questions

Does spdlog block the GUI thread when logging?

No, spdlog's Qt sinks use QMetaObject::invokeMethod with automatic connection types, which queues the update to the main thread's event loop rather than blocking the caller. Even when logging from worker threads, the actual widget update happens asynchronously on the GUI thread.

Can I use spdlog with Qt widgets in a multi-threaded application?

Yes, use the *_mt factory functions (such as qt_logger_mt or qt_color_logger_mt) which create sinks protected by std::mutex. According to the source code in include/spdlog/sinks/qt_sinks.h, these variants inherit from base_sink<std::mutex> and are safe for concurrent access from multiple threads.

How do I limit the number of lines in a Qt log widget?

Use qt_color_sink or qt_color_logger_mt and specify the max_lines parameter. The sink automatically removes the oldest QTextBlock entries when the count exceeds your threshold, preventing the QTextEdit from consuming unbounded memory during extended operation.

Is it possible to log to multiple targets including a Qt widget?

Yes, create a logger with multiple sinks. You can combine qt_sink with file sinks, console sinks, or network sinks using spdlog::sinks::dist_sink or by passing a vector of sinks to the logger constructor. Each sink receives the formatted message independently, allowing simultaneous file and GUI logging.

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 →