qt_sinks.h 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Copyright(c) 2015-present, Gabi Melman, mguludag and spdlog contributors.
  2. // Distributed under the MIT License (http://opensource.org/licenses/MIT)
  3. #pragma once
  4. //
  5. // Custom sink for QPlainTextEdit or QTextEdit and its childs(QTextBrowser...
  6. // etc) Building and using requires Qt library.
  7. //
  8. // Warning: the qt_sink won't be notified if the target widget is destroyed.
  9. // If the widget's lifetime can be shorter than the logger's one, you should provide some permanent QObject,
  10. // and then use a standard signal/slot.
  11. //
  12. #include "spdlog/common.h"
  13. #include "spdlog/details/log_msg.h"
  14. #include "spdlog/details/synchronous_factory.h"
  15. #include "spdlog/sinks/base_sink.h"
  16. #include <array>
  17. #include <QTextEdit>
  18. #include <QPlainTextEdit>
  19. //
  20. // qt_sink class
  21. //
  22. namespace spdlog {
  23. namespace sinks {
  24. template<typename Mutex>
  25. class qt_sink : public base_sink<Mutex>
  26. {
  27. public:
  28. qt_sink(QObject *qt_object, std::string meta_method)
  29. : qt_object_(qt_object)
  30. , meta_method_(std::move(meta_method))
  31. {
  32. if (!qt_object_)
  33. {
  34. throw_spdlog_ex("qt_sink: qt_object is null");
  35. }
  36. }
  37. ~qt_sink()
  38. {
  39. flush_();
  40. }
  41. protected:
  42. void sink_it_(const details::log_msg &msg) override
  43. {
  44. memory_buf_t formatted;
  45. base_sink<Mutex>::formatter_->format(msg, formatted);
  46. const string_view_t str = string_view_t(formatted.data(), formatted.size());
  47. QMetaObject::invokeMethod(qt_object_, meta_method_.c_str(), Qt::AutoConnection,
  48. Q_ARG(QString, QString::fromUtf8(str.data(), static_cast<int>(str.size())).trimmed()));
  49. }
  50. void flush_() override {}
  51. private:
  52. QObject *qt_object_ = nullptr;
  53. std::string meta_method_;
  54. };
  55. // QT color sink to QTextEdit.
  56. // Color location is determined by the sink log pattern like in the rest of spdlog sinks.
  57. // Colors can be modified if needed using sink->set_color(level, qtTextCharFormat).
  58. // max_lines is the maximum number of lines that the sink will hold before removing the oldest lines.
  59. // Note: Only ascii (latin1) is supported by this sink.
  60. template<typename Mutex>
  61. class qt_color_sink : public base_sink<Mutex>
  62. {
  63. public:
  64. qt_color_sink(QTextEdit *qt_text_edit, int max_lines)
  65. : qt_text_edit_(qt_text_edit)
  66. , max_lines_(max_lines)
  67. {
  68. if (!qt_text_edit_)
  69. {
  70. throw_spdlog_ex("qt_color_text_sink: text_edit is null");
  71. }
  72. default_color_ = qt_text_edit_->currentCharFormat();
  73. // set colors
  74. QTextCharFormat format;
  75. // trace
  76. format.setForeground(Qt::gray);
  77. colors_.at(level::trace) = format;
  78. // debug
  79. format.setForeground(Qt::cyan);
  80. colors_.at(level::debug) = format;
  81. // info
  82. format.setForeground(Qt::green);
  83. colors_.at(level::info) = format;
  84. // warn
  85. format.setForeground(Qt::yellow);
  86. colors_.at(level::warn) = format;
  87. // err
  88. format.setForeground(Qt::red);
  89. colors_.at(level::err) = format;
  90. // critical
  91. format.setForeground(Qt::white);
  92. format.setBackground(Qt::red);
  93. colors_.at(level::critical) = format;
  94. }
  95. ~qt_color_sink()
  96. {
  97. flush_();
  98. }
  99. void set_default_color(QTextCharFormat format)
  100. {
  101. // std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
  102. default_color_ = format;
  103. }
  104. void set_level_color(level::level_enum color_level, QTextCharFormat format)
  105. {
  106. // std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
  107. colors_.at(static_cast<size_t>(color_level)) = format;
  108. }
  109. QTextCharFormat &get_level_color(level::level_enum color_level)
  110. {
  111. std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
  112. return colors_.at(static_cast<size_t>(color_level));
  113. }
  114. QTextCharFormat &get_default_color()
  115. {
  116. std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
  117. return default_color_;
  118. }
  119. protected:
  120. struct invoke_params
  121. {
  122. invoke_params(int max_lines, QTextEdit *q_text_edit, QString payload, QTextCharFormat default_color, QTextCharFormat level_color,
  123. int color_range_start, int color_range_end)
  124. : max_lines(max_lines)
  125. , q_text_edit(q_text_edit)
  126. , payload(std::move(payload))
  127. , default_color(default_color)
  128. , level_color(level_color)
  129. , color_range_start(color_range_start)
  130. , color_range_end(color_range_end)
  131. {}
  132. int max_lines;
  133. QTextEdit *q_text_edit;
  134. QString payload;
  135. QTextCharFormat default_color;
  136. QTextCharFormat level_color;
  137. int color_range_start;
  138. int color_range_end;
  139. };
  140. void sink_it_(const details::log_msg &msg) override
  141. {
  142. memory_buf_t formatted;
  143. base_sink<Mutex>::formatter_->format(msg, formatted);
  144. const string_view_t str = string_view_t(formatted.data(), formatted.size());
  145. // apply the color to the color range in the formatted message.
  146. auto payload = QString::fromLatin1(str.data(), static_cast<int>(str.size()));
  147. invoke_params params{max_lines_, // max lines
  148. qt_text_edit_, // text edit to append to
  149. std::move(payload), // text to append
  150. default_color_, // default color
  151. colors_.at(msg.level), // color to apply
  152. static_cast<int>(msg.color_range_start), // color range start
  153. static_cast<int>(msg.color_range_end)}; // color range end
  154. QMetaObject::invokeMethod(
  155. qt_text_edit_, [params]() { invoke_method_(params); }, Qt::AutoConnection);
  156. }
  157. void flush_() override {}
  158. // Add colored text to the text edit widget. This method is invoked in the GUI thread.
  159. // It is a static method to ensure that it is handled correctly even if the sink is destroyed prematurely
  160. // before it is invoked.
  161. static void invoke_method_(invoke_params params)
  162. {
  163. auto *document = params.q_text_edit->document();
  164. QTextCursor cursor(document);
  165. // remove first blocks if number of blocks exceeds max_lines
  166. while (document->blockCount() > params.max_lines)
  167. {
  168. cursor.select(QTextCursor::BlockUnderCursor);
  169. cursor.removeSelectedText();
  170. cursor.deleteChar(); // delete the newline after the block
  171. }
  172. cursor.movePosition(QTextCursor::End);
  173. cursor.setCharFormat(params.default_color);
  174. // if color range not specified or not not valid, just append the text with default color
  175. if (params.color_range_end <= params.color_range_start)
  176. {
  177. cursor.insertText(params.payload);
  178. return;
  179. }
  180. // insert the text before the color range
  181. cursor.insertText(params.payload.left(params.color_range_start));
  182. // insert the colorized text
  183. cursor.setCharFormat(params.level_color);
  184. cursor.insertText(params.payload.mid(params.color_range_start, params.color_range_end - params.color_range_start));
  185. // insert the text after the color range with default format
  186. cursor.setCharFormat(params.default_color);
  187. cursor.insertText(params.payload.mid(params.color_range_end));
  188. }
  189. QTextEdit *qt_text_edit_;
  190. int max_lines_;
  191. QTextCharFormat default_color_;
  192. std::array<QTextCharFormat, level::n_levels> colors_;
  193. };
  194. #include "spdlog/details/null_mutex.h"
  195. #include <mutex>
  196. using qt_sink_mt = qt_sink<std::mutex>;
  197. using qt_sink_st = qt_sink<details::null_mutex>;
  198. using qt_color_sink_mt = qt_color_sink<std::mutex>;
  199. using qt_color_sink_st = qt_color_sink<details::null_mutex>;
  200. } // namespace sinks
  201. //
  202. // Factory functions
  203. //
  204. // log to QTextEdit
  205. template<typename Factory = spdlog::synchronous_factory>
  206. inline std::shared_ptr<logger> qt_logger_mt(const std::string &logger_name, QTextEdit *qt_object, const std::string &meta_method = "append")
  207. {
  208. return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method);
  209. }
  210. template<typename Factory = spdlog::synchronous_factory>
  211. inline std::shared_ptr<logger> qt_logger_st(const std::string &logger_name, QTextEdit *qt_object, const std::string &meta_method = "append")
  212. {
  213. return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method);
  214. }
  215. // log to QPlainTextEdit
  216. template<typename Factory = spdlog::synchronous_factory>
  217. inline std::shared_ptr<logger> qt_logger_mt(
  218. const std::string &logger_name, QPlainTextEdit *qt_object, const std::string &meta_method = "appendPlainText")
  219. {
  220. return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method);
  221. }
  222. template<typename Factory = spdlog::synchronous_factory>
  223. inline std::shared_ptr<logger> qt_logger_st(
  224. const std::string &logger_name, QPlainTextEdit *qt_object, const std::string &meta_method = "appendPlainText")
  225. {
  226. return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method);
  227. }
  228. // log to QObject
  229. template<typename Factory = spdlog::synchronous_factory>
  230. inline std::shared_ptr<logger> qt_logger_mt(const std::string &logger_name, QObject *qt_object, const std::string &meta_method)
  231. {
  232. return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method);
  233. }
  234. template<typename Factory = spdlog::synchronous_factory>
  235. inline std::shared_ptr<logger> qt_logger_st(const std::string &logger_name, QObject *qt_object, const std::string &meta_method)
  236. {
  237. return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method);
  238. }
  239. // log to QTextEdit with colorize output
  240. template<typename Factory = spdlog::synchronous_factory>
  241. inline std::shared_ptr<logger> qt_color_logger_mt(const std::string &logger_name, QTextEdit *qt_text_edit, int max_lines)
  242. {
  243. return Factory::template create<sinks::qt_color_sink_mt>(logger_name, qt_text_edit, max_lines);
  244. }
  245. template<typename Factory = spdlog::synchronous_factory>
  246. inline std::shared_ptr<logger> qt_color_logger_st(const std::string &logger_name, QTextEdit *qt_text_edit, int max_lines)
  247. {
  248. return Factory::template create<sinks::qt_color_sink_st>(logger_name, qt_text_edit, max_lines);
  249. }
  250. } // namespace spdlog