ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
LogWidget.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "LogWidget.h"
9 
10 #include "util/option_manager.h"
11 
12 // CV_CORE_LIB
13 #include <CVLog.h>
14 
15 // Qt5/Qt6 Compatibility
16 #include <QtCompat.h>
17 
18 namespace cloudViewer {
19 
20 LogWidget::LogWidget(QWidget* parent, const int max_num_blocks) {
21  setWindowFlags(Qt::Window);
22  setWindowTitle("Log");
23  resize(320, parent->height());
24 
25  QGridLayout* grid = new QGridLayout(this);
26  grid->setContentsMargins(5, 10, 5, 5);
27 
28  qRegisterMetaType<QTextCursor>("QTextCursor");
29  qRegisterMetaType<QTextBlock>("QTextBlock");
30 
31  QTimer* timer = new QTimer(this);
32  connect(timer, &QTimer::timeout, this, &LogWidget::Flush);
33  timer->start(100);
34 
35  // Comment these lines if debugging, otherwise debug messages won't appear
36  // on the console and the output is lost in the log widget when crashing
37  cout_redirector_ =
39  std::cout, LogWidget::Update, this);
40  cerr_redirector_ =
42  std::cerr, LogWidget::Update, this);
43  clog_redirector_ =
45  std::clog, LogWidget::Update, this);
46 
47  QHBoxLayout* left_button_layout = new QHBoxLayout();
48 
49  QPushButton* save_log_button = new QPushButton(tr("Save"), this);
50  connect(save_log_button, &QPushButton::released, this, &LogWidget::SaveLog);
51  left_button_layout->addWidget(save_log_button);
52 
53  QPushButton* clear_button = new QPushButton(tr("Clear"), this);
54  connect(clear_button, &QPushButton::released, this, &LogWidget::Clear);
55  left_button_layout->addWidget(clear_button);
56 
57  grid->addLayout(left_button_layout, 0, 0, Qt::AlignLeft);
58 
59  QHBoxLayout* right_button_layout = new QHBoxLayout();
60 
61  grid->addLayout(right_button_layout, 0, 1, Qt::AlignRight);
62 
63  text_box_ = new QPlainTextEdit(this);
64  text_box_->setReadOnly(true);
65  text_box_->setMaximumBlockCount(max_num_blocks);
66  text_box_->setWordWrapMode(QTextOption::NoWrap);
67  text_box_->setFont(QFont("Courier", 10));
68  grid->addWidget(text_box_, 1, 0, 1, 2);
69 }
70 
72  // Flush any remaining CVLog buffer content
73  if (!cvlog_buffer_.empty()) {
74  QString remaining = QString::fromStdString(cvlog_buffer_).trimmed();
75  if (!remaining.isEmpty()) {
77  }
78  }
79 
80  if (log_file_.is_open()) {
81  log_file_.close();
82  }
83 
84  delete cout_redirector_;
85  delete cerr_redirector_;
86  delete clog_redirector_;
87 }
88 
89 void LogWidget::Append(const std::string& text) {
90  // Collect CVLog messages to send after releasing the lock
91  // to avoid deadlock when CVLog::LogMessage triggers GUI dialogs
92  QList<QPair<QString, int>> cvlog_messages;
93 
94  {
95  QMutexLocker locker(&mutex_);
96  text_queue_ += text;
97 
98  // Dump to log file and flush immediately to avoid data loss on crash
99  if (log_file_.is_open()) {
100  log_file_ << text;
101  log_file_.flush(); // Immediate flush for crash safety
102  }
103 
104  // Also output to CVLog for unified logging (accumulate by lines)
105  cvlog_buffer_ += text;
106 
107  // Process complete lines in buffer
108  size_t pos;
109  while ((pos = cvlog_buffer_.find('\n')) != std::string::npos) {
110  std::string line = cvlog_buffer_.substr(0, pos);
111  cvlog_buffer_.erase(0, pos + 1);
112 
113  if (!line.empty()) {
114  QString qline = QString::fromStdString(line);
115  qline = qline.trimmed();
116 
117  if (!qline.isEmpty()) {
118  // Determine log level based on content
119  int logLevel = CVLog::LOG_STANDARD;
120  QString lowerLine = qline.toLower();
121 
122  // Use QtCompatRegExp for Qt5/Qt6 compatibility
123  static const QtCompatRegExp errorPattern(
124  "\\b(error|fatal|exception|crash)"
125  "\\b",
127  static const QtCompatRegExp warningPattern(
128  "\\b(warning|warn|caution)\\b",
130 
131  // Use qtCompatRegExpMatch for cross-version compatibility
132  bool hasError =
133  qtCompatRegExpMatch(errorPattern, lowerLine);
134  bool hasWarning =
135  qtCompatRegExpMatch(warningPattern, lowerLine);
136 
137  // Also check for common log format prefixes
138  if (lowerLine.startsWith("error") ||
139  lowerLine.startsWith("[error") ||
140  lowerLine.startsWith("e ") || hasError) {
141  logLevel = CVLog::LOG_ERROR;
142  } else if (lowerLine.startsWith("warning") ||
143  lowerLine.startsWith("[warning") ||
144  lowerLine.startsWith("w ") || hasWarning) {
145  logLevel = CVLog::LOG_WARNING;
146  }
147 
148  // Collect message to send after releasing lock
149  cvlog_messages.append(qMakePair(qline, logLevel));
150  }
151  }
152  }
153  // Also flush incomplete buffer periodically for crash safety
154  // If buffer is getting large (>1KB) without newline, flush it anyway
155  if (cvlog_buffer_.size() > 1024) {
156  QString remaining = QString::fromStdString(cvlog_buffer_).trimmed();
157  if (!remaining.isEmpty()) {
158  cvlog_messages.append(
159  qMakePair(remaining, CVLog::LOG_STANDARD));
160  }
161  cvlog_buffer_.clear();
162  }
163  } // Release lock here
164 
165  // Now send CVLog messages without holding the lock
166  // This prevents deadlock when CVLog::LogMessage triggers GUI dialogs
167  for (const auto& msg : cvlog_messages) {
168  CVLog::LogMessage(msg.first, msg.second);
169  }
170 }
171 
173  QMutexLocker locker(&mutex_);
174 
175  if (text_queue_.size() > 0) {
176  // Write to log widget
177  text_box_->moveCursor(QTextCursor::End);
178  text_box_->insertPlainText(QString::fromStdString(text_queue_));
179  text_box_->moveCursor(QTextCursor::End);
180  text_queue_.clear();
181  }
182 }
183 
185  QMutexLocker locker(&mutex_);
186  text_queue_.clear();
187  cvlog_buffer_.clear();
188  text_box_->clear();
189 }
190 
191 void LogWidget::Update(const char* text,
192  std::streamsize count,
193  void* log_widget_ptr) {
194  std::string text_str;
195  for (std::streamsize i = 0; i < count; ++i) {
196  if (text[i] == '\n') {
197  text_str += "\n";
198  } else {
199  text_str += text[i];
200  }
201  }
202 
203  LogWidget* log_widget = static_cast<LogWidget*>(log_widget_ptr);
204  log_widget->Append(text_str);
205 }
206 
207 void LogWidget::SaveLog() {
208  const std::string log_path =
209  QFileDialog::getSaveFileName(this, tr("Select path to log file"),
210  "", tr("Log (*.log)"))
211  .toUtf8()
212  .constData();
213 
214  if (log_path == "") {
215  return;
216  }
217 
218  std::ofstream file(log_path, std::ios::app);
219  CHECK(file.is_open()) << log_path;
220  file << text_box_->toPlainText().toUtf8().constData();
221 }
222 
223 } // namespace cloudViewer
int count
QRegularExpression QtCompatRegExp
Definition: QtCompat.h:170
bool qtCompatRegExpMatch(const QRegularExpression &regex, const QString &str)
Definition: QtCompat.h:446
static void LogMessage(const QString &message, int level)
Static shortcut to CVLog::logMessage.
Definition: CVLog.cpp:64
@ LOG_STANDARD
Definition: CVLog.h:44
@ LOG_WARNING
Definition: CVLog.h:46
@ LOG_ERROR
Definition: CVLog.h:47
LogWidget(QWidget *parent, const int max_num_blocks=100000)
Definition: LogWidget.cpp:20
void Append(const std::string &text)
Definition: LogWidget.cpp:89
constexpr QRegularExpression::PatternOption CaseInsensitive
Definition: QtCompat.h:174
Generic file read and write utility for python interface.
struct Window Window
Definition: sqlite3.c:14678
double timer
Definition: struct.h:215