swift
Loading...
Searching...
No Matches
loghandler.cpp
1// SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
2// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3
5
6#include "misc/loghandler.h"
7
9#include "misc/algorithm.h"
11#include "misc/threadutils.h"
12
13#ifdef SWIFT_USE_CRASHPAD
14# include "crashpad/client/simulate_crash.h"
15#endif
16
17#include <algorithm>
18
19#include <QAbstractNativeEventFilter>
20#include <QCoreApplication>
21#include <QGlobalStatic>
22#include <QMessageLogContext>
23#include <QMetaMethod>
24#include <QString>
25#include <Qt>
26#include <QtDebug>
27
28#ifdef Q_OS_WIN
29# include <windows.h>
30#endif
31
32namespace swift::misc
33{
34 Q_GLOBAL_STATIC(CLogHandler, g_handler)
35
37 {
38 Q_ASSERT(!g_handler.isDestroyed());
39 return g_handler;
40 }
41
43 void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
44 {
45 if (type == QtFatalMsg && CLogHandler::instance()->thread() != QThread::currentThread())
46 {
47 // Fatal message means this thread is about to crash the application. A queued connection would be useless.
48 // Blocking queued connection means we pause this thread just long enough to let the main thread handle the
49 // message.
51 CLogHandler::instance(), [&] { messageHandler(type, context, message); }, Qt::BlockingQueuedConnection);
52 return;
53 }
54#if defined(Q_CC_MSVC) && defined(QT_NO_DEBUG)
55 if (type == QtFatalMsg)
56 {
57 struct EventFilter : public QAbstractNativeEventFilter
58 {
59 // Prevent Qt from handling Windows Messages while the messagebox is open
60 virtual bool nativeEventFilter(const QByteArray &, void *message, qintptr *result) override
61 {
62 auto msg = static_cast<MSG *>(message);
63 *result = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
64 return true;
65 }
66 };
67 EventFilter ef;
68 qApp->installNativeEventFilter(&ef);
69 MessageBoxW(nullptr, message.toStdWString().c_str(), nullptr,
70 MB_OK); // display assert dialog in release build
71 qApp->removeNativeEventFilter(&ef);
72# if defined(SWIFT_USE_CRASHPAD)
73 CRASHPAD_SIMULATE_CRASH(); // workaround inability to catch __fastfail
74# endif
75 }
76#endif
77 QMetaObject::invokeMethod(CLogHandler::instance(), [statusMessage = CStatusMessage(type, context, message)] {
78 CLogHandler::instance()->logLocalMessage(statusMessage);
79 });
80 }
81
82 void CLogHandler::install(bool skipIfAlreadyInstalled)
83 {
84 if (skipIfAlreadyInstalled && m_oldHandler)
85 {
86 return;
87 }
88 Q_ASSERT_X(!m_oldHandler, Q_FUNC_INFO, "Re-installing the log handler should be avoided");
89 m_oldHandler = qInstallMessageHandler(messageHandler);
90 }
91
92 CLogHandler::CLogHandler()
93 {
94 // in case the first call to instance() is in a different thread
96 }
97
98 CLogHandler::~CLogHandler() { qInstallMessageHandler(m_oldHandler); }
99
101 {
102 Q_ASSERT(thread() == QThread::currentThread());
103
104 auto finder = [&](const PatternPair &pair) { return pair.first == pattern; };
105 auto comparator = [](const PatternPair &a, const PatternPair &b) { return a.first.isProperSubsetOf(b.first); };
106
107 auto it = std::ranges::find_if(m_patternHandlers, finder);
108 if (it == m_patternHandlers.cend())
109 {
110 auto *handler = new CLogPatternHandler(this, pattern);
111 topologicallySortedInsert(m_patternHandlers, PatternPair(pattern, handler), comparator);
112 return handler;
113 }
114 else
115 {
116 return (*it).second;
117 }
118 }
119
121 {
123 }
124
125 QList<CLogPatternHandler *> CLogHandler::handlersForMessage(const CStatusMessage &message) const
126 {
127 QList<CLogPatternHandler *> m_handlers;
128 for (const auto &pair : m_patternHandlers)
129 {
130 if (pair.first.match(message))
131 {
132 m_handlers.push_back(pair.second);
133 }
134 }
135 return m_handlers;
136 }
137
138 bool CLogHandler::isFallThroughEnabled(const QList<CLogPatternHandler *> &handlers) const
139 {
140 for (const auto *handler : handlers)
141 {
142 if (!handler->m_inheritFallThrough)
143 {
144 return handler->m_enableFallThrough;
145 }
146 }
147 return m_enableFallThrough;
148 }
149
150 void CLogHandler::logLocalMessage(const CStatusMessage &i_statusMessage)
151 {
152 using namespace swift::config;
153 CStatusMessage statusMessage = i_statusMessage;
154 if (!CBuildConfig::isLocalDeveloperDebugBuild() &&
155 CLogPattern::empty().withSeverity(CStatusMessage::SeverityError).match(statusMessage))
156 {
157 // 99% this is a complex Qt implementation warning generated by qErrnoWarning, so downgrade its severity
158 statusMessage.setSeverity(CStatusMessage::SeverityDebug);
159 }
160
161 if (!CBuildConfig::isLocalDeveloperDebugBuild() &&
162 CLogPattern::exactMatch("default").withSeverity(CStatusMessage::SeverityWarning).match(statusMessage))
163 {
164 // All Qt warnings
165
166 // demoted because caused by airline icons, we would need to re-create literally dozens of these images
167 if (statusMessage.getMessage().startsWith(QStringLiteral("libpng warning")))
168 {
169 statusMessage.setSeverity(CStatusMessage::SeverityDebug);
170 }
171
172 // demoted, because in some swift APPs some options can be ignored
173 else if (statusMessage.getMessage().startsWith(QStringLiteral("QCommandLineParser: option not defined")))
174 {
175 statusMessage.setSeverity(CStatusMessage::SeverityDebug);
176 }
177 }
178
179 auto bucket = m_tokenBuckets.find(statusMessage);
180 if (bucket == m_tokenBuckets.end())
181 {
182 bucket = m_tokenBuckets.insert(statusMessage, { { 5, 1000, 1 }, 0 });
183 }
184 if (!bucket->first.tryConsume())
185 {
186 bucket->second++;
187 return;
188 }
189 if (bucket->second > 0)
190 {
191 auto copy = statusMessage;
192 copy.appendMessage(QStringLiteral(" (+%1 identical messages)").arg(bucket->second));
193 bucket->second = 0;
194
195 logMessage(copy);
196 emit localMessageLogged(copy);
197 return;
198 }
199
200 logMessage(statusMessage);
201 emit localMessageLogged(statusMessage);
202 }
203
204 void CLogHandler::logRemoteMessage(const CStatusMessage &statusMessage)
205 {
206 logMessage(statusMessage);
207 emit remoteMessageLogged(statusMessage);
208 }
209
211 {
212 Q_ASSERT_X(m_oldHandler, Q_FUNC_INFO, "Install the log handler before using it");
213 Q_ASSERT_X(thread() == QThread::currentThread(), Q_FUNC_INFO, "Wrong thread");
214 m_enableFallThrough = enable;
215 }
216
217 void CLogHandler::logMessage(const CStatusMessage &statusMessage)
218 {
219 const auto handlers = handlersForMessage(statusMessage);
220
221 if (isFallThroughEnabled(handlers))
222 {
223 Q_ASSERT_X(m_oldHandler, Q_FUNC_INFO, "Handler must be installed");
224 QtMsgType type {};
225 QString category;
226 QString message;
227 statusMessage.toQtLogTriple(&type, &category, &message);
228 m_oldHandler(type, QMessageLogContext(nullptr, 0, nullptr, qPrintable(category)), message);
229 }
230
231 for (auto *handler : handlers)
232 {
233 emit handler->messageLogged(statusMessage);
234 }
235 }
236
237 void CLogHandler::removePatternHandler(CLogPatternHandler *handler)
238 {
239 auto it = std::ranges::find_if(m_patternHandlers,
240 [handler](const PatternPair &pair) { return pair.second == handler; });
241 if (it != m_patternHandlers.end())
242 {
243 it->second->deleteLater();
244 m_patternHandlers.erase(it);
245 }
246 }
247
248 QList<CLogPattern> CLogHandler::getAllSubscriptions() const
249 {
250 QList<CLogPattern> result;
251 for (const auto &pair : m_patternHandlers)
252 {
253 if (pair.second->isSignalConnected(QMetaMethod::fromSignal(&CLogPatternHandler::messageLogged)))
254 {
255 result.push_back(pair.first);
256 }
257 }
258 return result;
259 }
260
261 CLogPatternHandler::CLogPatternHandler(CLogHandler *parent, const CLogPattern &pattern)
262 : QObject(parent), m_parent(parent), m_pattern(pattern)
263 {
264 connect(&m_subscriptionUpdateTimer, &QTimer::timeout, this, &CLogPatternHandler::updateSubscription);
265 m_subscriptionUpdateTimer.start(1);
266 }
267
268 void CLogPatternHandler::updateSubscription()
269 {
270 if (m_subscriptionNeedsUpdate)
271 {
272 m_subscriptionNeedsUpdate = false;
273 bool isSubscribed = isSignalConnected(QMetaMethod::fromSignal(&CLogPatternHandler::messageLogged));
274
275 if (isSubscribed != m_isSubscribed)
276 {
277 m_isSubscribed = isSubscribed;
278 if (m_isSubscribed)
279 {
280 emit m_parent->subscriptionAdded(m_pattern);
281 }
282 else
283 {
284 emit m_parent->subscriptionRemoved(m_pattern);
285 }
286 }
287
288 if (m_inheritFallThrough && !m_isSubscribed)
289 {
290 m_parent->removePatternHandler(this);
291 }
292 }
293 }
294
295 void CLogSubscriber::changeSubscription(const CLogPattern &pattern)
296 {
297 if (CLogHandler::instance()->thread() != QThread::currentThread())
298 {
299 Q_ASSERT(thread() == QThread::currentThread());
300 singleShot(0, CLogHandler::instance(), [pattern, self = QPointer<CLogSubscriber>(this)]() {
301 if (self)
302 {
303 self->changeSubscription(pattern);
304 }
305 });
306 return;
307 }
308
309 unsubscribe();
310 m_handler = CLogHandler::instance()->handlerForPattern(pattern);
311
312 if (!m_inheritFallThrough)
313 {
314 m_handler->enableConsoleOutput(m_enableFallThrough);
315 }
316 connect(m_handler.data(), &CLogPatternHandler::messageLogged, this, &CLogSubscriber::ps_logMessage,
318 }
319
320 void CLogSubscriber::unsubscribe()
321 {
322 if (CLogHandler::instance()->thread() != QThread::currentThread())
323 {
324 Q_ASSERT(thread() == QThread::currentThread());
325 singleShot(0, CLogHandler::instance(), [self = QPointer<CLogSubscriber>(this)]() {
326 if (self)
327 {
328 self->unsubscribe();
329 }
330 });
331 return;
332 }
333
334 if (m_handler)
335 {
336 if (!m_inheritFallThrough)
337 {
338 m_handler->inheritConsoleOutput();
339 }
340 m_handler->disconnect(this);
341 }
342 }
343
344 void CLogSubscriber::inheritConsoleOutput()
345 {
346 if (CLogHandler::instance()->thread() != QThread::currentThread())
347 {
348 Q_ASSERT(thread() == QThread::currentThread());
349 singleShot(0, CLogHandler::instance(), [self = QPointer<CLogSubscriber>(this)]() {
350 if (self)
351 {
352 self->inheritConsoleOutput();
353 }
354 });
355 return;
356 }
357
358 m_inheritFallThrough = true;
359 if (m_handler)
360 {
361 m_handler->inheritConsoleOutput();
362 }
363 }
364
365 void CLogSubscriber::enableConsoleOutput(bool enable)
366 {
367 if (CLogHandler::instance()->thread() != QThread::currentThread())
368 {
369 Q_ASSERT(thread() == QThread::currentThread());
370 singleShot(0, CLogHandler::instance(), [enable, self = QPointer<CLogSubscriber>(this)]() {
371 if (self)
372 {
373 self->enableConsoleOutput(enable);
374 }
375 });
376 return;
377 }
378
379 m_inheritFallThrough = false;
380 m_enableFallThrough = enable;
381 if (m_handler)
382 {
383 m_handler->enableConsoleOutput(enable);
384 }
385 }
386} // namespace swift::misc
387
A log category is an arbitrary string tag which can be attached to log messages.
Definition logcategory.h:28
Class for subscribing to log messages.
Definition loghandler.h:37
void localMessageLogged(const swift::misc::CStatusMessage &message)
Emitted when a message is logged in this process.
void enableConsoleOutput(bool enable)
Enable or disable the default Qt handler.
static CLogHandler * instance()
Return pointer to the CLogHandler singleton.
CLogPatternHandler * handlerForCategory(const CLogCategory &category)
Return a pattern handler for subscribing to all messages which contain the given category.
QList< CLogPattern > getAllSubscriptions() const
Returns all log patterns for which there are currently subscribed log pattern handlers.
void logLocalMessage(const swift::misc::CStatusMessage &message)
Called by our QtMessageHandler to log a message.
void logRemoteMessage(const swift::misc::CStatusMessage &message)
Called by the context to relay a message.
void remoteMessageLogged(const swift::misc::CStatusMessage &message)
Emitted when a log message is relayed from a different process.
void install(bool skipIfAlreadyInstalled=false)
Tell the CLogHandler to install itself with qInstallMessageHandler.
CLogPatternHandler * handlerForPattern(const CLogPattern &pattern)
Return a pattern handler for subscribing to all messages which match the given pattern.
A class for subscribing to log messages which match particular patterns.
Definition loghandler.h:114
void messageLogged(const swift::misc::CStatusMessage &message)
Emitted when a message is logged which matches the relevant pattern.
Value class for matching log messages based on their categories.
Definition logpattern.h:49
static CLogPattern exactMatch(const CLogCategory &category)
Returns a CLogPattern which will match any message with the given category.
static CLogPattern empty()
Returns a CLogPattern which will match any message without a category.
Streamable status message, e.g.
static constexpr auto SeverityError
Status severities.
static constexpr auto SeverityDebug
Status severities.
static constexpr auto SeverityWarning
Status severities.
Free functions in swift::misc.
void topologicallySortedInsert(C &container, T &&value, F comparator)
Insert an element into a sequential container while preserving the topological ordering of the contai...
Definition algorithm.h:151
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition threadutils.h:30
QCoreApplication * instance()
void push_back(QList< T >::parameter_type value)
QMetaMethod fromSignal(PointerToMemberFunction signal)
bool invokeMethod(QObject *context, Functor &&function, Args &&... arguments)
bool moveToThread(QThread *targetThread)
QThread * thread() const const
std::wstring toStdWString() const const
BlockingQueuedConnection
QFuture< QtFuture::ArgsType< Signal > > connect(Sender *sender, Signal signal)
QThread * currentThread()
void timeout()