How to Use spdlog Qt Sinks for GUI Logging: A Complete Guide
spdlog Qt sinks provide thread-safe integration with Qt widgets, allowing you to redirect log messages directly to QTextEdit, QPlainTextEdit, or custom QObject slots via QMetaObject::invokeMethod.
The gabime/spdlog library offers specialized spdlog Qt sinks that bridge high-performance C++ logging with Qt's GUI framework. These sinks, defined in include/spdlog/sinks/qt_sinks.h, enable real-time log display in Qt applications by dispatching formatted messages to the GUI thread through Qt's meta-object system.
What Are spdlog Qt Sinks?
spdlog provides two Qt-specific sink implementations that inherit from spdlog::sinks::base_sink<Mutex>:
-
qt_sink: Sends formatted text to anyQObjectimplementing a slot (such asappendorappendPlainText). It forwards strings viaQMetaObject::invokeMethoddefined ininclude/spdlog/sinks/qt_sinks.h. -
qt_color_sink: Extendsqt_sinkfunctionality by applying color formatting based on log levels. It maintains a fixed-size buffer (max_lines) and supports UTF-8 strings.
Both come in thread-safety variants: *_mt (mutex-protected) for multi-threaded environments and *_st (single-threaded) for better performance when thread safety is not required.
Setting Up Basic Logging to QTextEdit
To create a logger that writes to a QTextEdit, use the qt_logger_mt factory function. This creates a multithreaded logger that invokes the specified slot on the target widget:
#include <QApplication>
#include <QTextEdit>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/qt_sinks.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTextEdit edit;
edit.show();
// Create a multithreaded logger that appends to QTextEdit::append()
auto logger = spdlog::qt_logger_mt("qt", &edit, "append");
logger->set_pattern("%^[%L] %v%$"); // colour tags will be ignored by this sink
logger->info("Application started");
logger->warn("This is a warning");
logger->error("Something went wrong!");
return a.exec();
}
The qt_logger_mt function is defined at lines 45-52 of include/spdlog/sinks/qt_sinks.h. It instantiates a qt_sink_mt internally and wraps it in a spdlog::logger using the default synchronous_factory.
Using QPlainTextEdit for Plain Text Output
For plain-text logging without HTML overhead, target QPlainTextEdit using the appendPlainText slot:
QPlainTextEdit *plain = new QPlainTextEdit;
auto logger = spdlog::qt_logger_st("plain", plain, "appendPlainText");
logger->debug("Debug line");
This approach avoids the rich-text processing overhead of QTextEdit::append(), making it suitable for high-volume logging scenarios.
Implementing Color-Aware Logging
The qt_color_sink applies ANSI color codes from your pattern to Qt's text color API. Use qt_color_logger_mt (lines 89-96 in qt_sinks.h) to create a color-aware logger:
QTextEdit *edit = new QTextEdit;
int maxLines = 500; // keep at most 500 lines
auto logger = spdlog::qt_color_logger_mt("colored", edit, maxLines, /*is_utf8=*/true);
// Example pattern – the part inside {} determines colour range
logger->set_pattern("[%L] %v");
// Emit coloured messages
logger->info("Info message");
logger->warn("Warning with {} placeholder"); // {} will be coloured as WARN
logger->error("Error {}!", "failure"); // colored part is "failure!"
The color mapping (trace → gray, debug → cyan, info → green, warn → yellow, error → red, critical → white on red) is established in the qt_color_sink constructor (lines 80-100 of the header).
Choosing Thread Safety Variants
spdlog Qt sinks provide explicit thread-safety controls through template specializations:
qt_logger_mt / qt_color_logger_mt: Use base_sink<std::mutex> to protect the sink_it_ method. Essential when worker threads generate log messages.
qt_logger_st / qt_color_logger_st: Use base_sink<spdlog::details::null_mutex> for single-threaded scenarios. These variants eliminate mutex overhead but require you to guarantee that only one thread accesses the logger.
// Multithreaded (mutex protected) – safe if logger is used from several threads
auto mt_logger = spdlog::qt_logger_mt("mt", &edit);
// Single-threaded – slightly faster, but you must guarantee only one thread uses it
auto st_logger = spdlog::qt_logger_st("st", &edit);
The threading model is implemented in base_sink.h through the Mutex template parameter, while the actual GUI dispatch always occurs on the main thread via QMetaObject::invokeMethod with Qt::AutoConnection.
Managing Widget Lifetime
The Qt sink does not track the lifetime of the target widget. If the widget is destroyed before the logger, undefined behavior occurs. The header issues a warning about this on lines 10-12.
To safely manage lifetimes, ensure the logger is destroyed before the widget:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow() {
logView = new QTextEdit(this);
setCentralWidget(logView);
logger = spdlog::qt_logger_mt("gui", logView);
}
~MainWindow() { spdlog::drop("gui"); } // destroy logger before the widget
private:
QTextEdit *logView;
std::shared_ptr<spdlog::logger> logger;
};
Alternatively, pass a permanent QObject (such as the main window) and connect a custom slot that forwards to the actual widget, ensuring the receiver object remains valid throughout the logger's lifetime.
Summary
- spdlog Qt sinks live in
include/spdlog/sinks/qt_sinks.hand provideqt_sinkandqt_color_sinkimplementations. - Use factory functions
qt_logger_mt,qt_logger_st,qt_color_logger_mt, orqt_color_logger_stto create loggers bound to Qt widgets. - Messages are dispatched to the GUI thread via
QMetaObject::invokeMethod, ensuring thread-safe UI updates from any thread. - Choose
*_mtvariants for multi-threaded applications and*_stfor single-threaded optimization. - Always ensure the target widget outlives the logger, or explicitly call
spdlog::drop(logger_name)before widget destruction.
Frequently Asked Questions
What is the difference between qt_sink and qt_color_sink?
qt_sink forwards raw formatted text to Qt widgets using QMetaObject::invokeMethod, while qt_color_sink additionally parses msg.color_range_start and msg.color_range_end from the spdlog::details::log_msg structure to apply level-specific colors to the text. The color sink maintains an internal buffer limit (max_lines) to prevent unbounded memory growth in long-running GUI applications.
How do I prevent crashes when my Qt widget is destroyed?
You must ensure the logger is destroyed before its target widget. As noted in lines 10-12 of qt_sinks.h, the sink holds a raw pointer to the QObject and does not track its lifetime. Call spdlog::drop(logger_name) in your widget's destructor or encapsulate the logger in a class that outlives the widget to prevent dangling pointer dereferences.
Can I use spdlog Qt sinks with custom QObject slots?
Yes. The factory functions accept any slot name as the third parameter. For example, spdlog::qt_logger_mt("custom", myObject, "customLogSlot") will invoke QMetaObject::invokeMethod on myObject with the slot name "customLogSlot", passing the formatted log message as a QString parameter. This allows you to implement custom filtering, formatting, or redirection logic in your own QObject subclasses.
Which thread safety variant should I choose for my application?
Use qt_logger_mt or qt_color_logger_mt when your application logs from multiple threads (such as worker threads or background tasks). These use std::mutex to protect the internal sink_it_ method. Use qt_logger_st or qt_color_logger_st only when you can guarantee all logging occurs from the main GUI thread, as these variants avoid mutex overhead but are not thread-safe.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →