diff --git a/include/spdlog/sinks/qt_sinks.h b/include/spdlog/sinks/qt_sinks.h index d61c6bec..11ea41ac 100644 --- a/include/spdlog/sinks/qt_sinks.h +++ b/include/spdlog/sinks/qt_sinks.h @@ -16,9 +16,12 @@ #include "spdlog/details/log_msg.h" #include "spdlog/details/synchronous_factory.h" #include "spdlog/sinks/base_sink.h" +#include +#include #include #include +#include // // qt_sink class @@ -45,9 +48,14 @@ protected: { memory_buf_t formatted; base_sink::formatter_->format(msg, formatted); + auto start = std::chrono::system_clock::now(); string_view_t str = string_view_t(formatted.data(), formatted.size()); QMetaObject::invokeMethod(qt_object_, meta_method_.c_str(), Qt::AutoConnection, Q_ARG(QString, QString::fromUtf8(str.data(), static_cast(str.size())).trimmed())); + auto elapsed = std::chrono::system_clock::now() - start; + + std::cout << "qt_sink::sink_it_ took " + << std::chrono::duration_cast(elapsed).count() << " micros\n"; } void flush_() override {} @@ -57,15 +65,189 @@ private: std::string meta_method_; }; +// QT color sink to QTextEdit. +// Color location is determined by the sink log pattern like in the rest of spdlog sinks. +// Colors can be modified if needed using sink->set_color(level, qtTextCharFormat). +// Note: Only ascii (latin1) is supported by this sink. + template + class qt_color_sink : public base_sink + { + public: + qt_color_sink(QTextEdit *qt_text_edit, int max_lines) + : qt_text_edit_(qt_text_edit) + { + if (!qt_text_edit_) + { + throw_spdlog_ex("qt_color_text_sink: text_edit is null"); + } + max_lines_ = max_lines; + default_color_ = qt_text_edit_->currentCharFormat(); + // set colors + QTextCharFormat format; + // trace + format.setForeground(Qt::gray); + colors_.at(level::trace) = format; + // debug + format.setForeground(Qt::cyan); + colors_.at(level::debug) = format; + // info + format.setForeground(Qt::green); + colors_.at(level::info) = format; + // warn + format.setForeground(Qt::yellow); + colors_.at(level::warn) = format; + // err + format.setForeground(Qt::red); + colors_.at(level::err) = format; + // critical + format.setForeground(Qt::white); + format.setBackground(Qt::red); + colors_.at(level::critical) = format; + } + + ~qt_color_sink() + { + flush_(); + } + + void set_default_color(QTextCharFormat format) + { + // std::lock_guard lock(base_sink::mutex_); + default_color_ = format; + } + + void set_level_color(level::level_enum color_level, QTextCharFormat format) + { + // std::lock_guard lock(base_sink::mutex_); + colors_.at(static_cast(color_level)) = format; + } + + QTextCharFormat &get_level_color(level::level_enum color_level) + { + std::lock_guard lock(base_sink::mutex_); + return colors_.at(static_cast(color_level)); + } + + QTextCharFormat &get_default_color() + { + std::lock_guard lock(base_sink::mutex_); + return default_color_; + } + + protected: + struct invoke_params + { + invoke_params(int max_lines, QTextEdit *q_text_edit, QString payload, QTextCharFormat default_color, + QTextCharFormat level_color, int color_range_start, int color_range_end) + : max_lines(max_lines), + q_text_edit(q_text_edit), + payload(std::move(payload)), + default_color(default_color), + level_color(level_color), + color_range_start(color_range_start), + color_range_end(color_range_end) + { + } + int max_lines; + QTextEdit *q_text_edit; + QString payload; + QTextCharFormat default_color; + QTextCharFormat level_color; + int color_range_start; + int color_range_end; + }; + void sink_it_(const details::log_msg &msg) override + { + + using this_type = qt_color_sink; + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + + auto start = std::chrono::system_clock::now(); + string_view_t str = string_view_t(formatted.data(), formatted.size()); + // apply the color to the color range in the formatted message. + auto payload = QString::fromLatin1(str.data(), static_cast(str.size())); + + // if color needed, apply it as + if (msg.color_range_end > msg.color_range_start) + { + invoke_params params { + max_lines_, // max lines + qt_text_edit_, // text edit to append to + std::move(payload), // text to append + default_color_, // default color + colors_.at(msg.level), // color to apply + msg.color_range_start, // color range start + msg.color_range_end}; // color range end + QMetaObject::invokeMethod( + qt_text_edit_, + [params]() {invoke_method_(params);}, + Qt::AutoConnection); + } + else // no color range, just append the text + { + auto *qt_text_edit = qt_text_edit_; + QMetaObject::invokeMethod( + qt_text_edit_, + [qt_text_edit, payload]() {qt_text_edit->append(payload.trimmed());}, + Qt::AutoConnection); + } + auto elapsed = std::chrono::duration_cast(std::chrono::system_clock::now() - start); + std::cout << "qt_color_sink::sink_it_ took " << elapsed.count() << " micros" << std::endl; + } + + void flush_() override {} + + // make this static so even if the sink get destroyed we can still can use it + static void invoke_method_(invoke_params params) + { + auto *document = params.q_text_edit->document(); + QTextCursor cursor(document); + + // remove first blocks if number of blocks exceeds max_lines + while(document->blockCount() > params.max_lines) + { + cursor.select(QTextCursor::BlockUnderCursor); + cursor.removeSelectedText(); + cursor.deleteChar(); // delete the newline after the block + } + + + cursor.movePosition(QTextCursor::End); + + // insert the text before the color range + cursor.setCharFormat(params.default_color); + cursor.insertText(params.payload.left(params.color_range_start)); + + // insert the colorized text + + cursor.setCharFormat(params.level_color); + cursor.insertText(params.payload.mid(params.color_range_start, params.color_range_end - params.color_range_start)); + + // insert the text after the color range with default format + cursor.setCharFormat(params.default_color); + cursor.insertText(params.payload.mid(params.color_range_end)); + } + + QTextEdit *qt_text_edit_; + int max_lines_; + QTextCharFormat default_color_; + std::array colors_; + }; + #include "spdlog/details/null_mutex.h" #include using qt_sink_mt = qt_sink; -using qt_sink_st = qt_sink; +using qt_sink_st = qt_sink; +using qt_color_sink_mt = qt_color_sink; +using qt_color_sink_st = qt_color_sink; } // namespace sinks // // Factory functions // + +// log to QTextEdit template inline std::shared_ptr qt_logger_mt(const std::string &logger_name, QTextEdit *qt_object, const std::string &meta_method = "append") { @@ -78,6 +260,7 @@ inline std::shared_ptr qt_logger_st(const std::string &logger_name, QTex return Factory::template create(logger_name, qt_object, meta_method); } +// log to QPlainTextEdit template inline std::shared_ptr qt_logger_mt( const std::string &logger_name, QPlainTextEdit *qt_object, const std::string &meta_method = "appendPlainText") @@ -91,7 +274,7 @@ inline std::shared_ptr qt_logger_st( { return Factory::template create(logger_name, qt_object, meta_method); } - +// log to QObject template inline std::shared_ptr qt_logger_mt(const std::string &logger_name, QObject *qt_object, const std::string &meta_method) { @@ -103,4 +286,18 @@ inline std::shared_ptr qt_logger_st(const std::string &logger_name, QObj { return Factory::template create(logger_name, qt_object, meta_method); } + +// log to QTextEdit with colorize output +template +inline std::shared_ptr qt_color_logger_mt(const std::string &logger_name, QTextEdit *qt_text_edit, int max_lines=5000) +{ + return Factory::template create(logger_name, qt_text_edit, max_lines); +} + +template +inline std::shared_ptr qt_color_logger_st(const std::string &logger_name, QTextEdit *qt_text_edit, int max_lines=5000) +{ + return Factory::template create(logger_name, qt_text_edit); +} + } // namespace spdlog