Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qloggingregistry.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
6
7#include <QtCore/qfile.h>
8#include <QtCore/qlibraryinfo.h>
9#include <QtCore/private/qlocking_p.h>
10#include <QtCore/qscopedvaluerollback.h>
11#include <QtCore/qstandardpaths.h>
12#include <QtCore/qstringtokenizer.h>
13#include <QtCore/qdir.h>
14#include <QtCore/qcoreapplication.h>
15#include <qplatformdefs.h>
16
17#if QT_CONFIG(settings)
18#include <QtCore/qsettings.h>
19#include <QtCore/private/qsettings_p.h>
20#endif
21
22// We can't use the default macros because this would lead to recursion.
23// Instead let's define our own one that unconditionally logs...
24#define debugMsg QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC, "qt.core.logging").debug
25#define warnMsg QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC, "qt.core.logging").warning
26#define registryMsg QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC, "_logging_categories").debug
27
29
30using namespace Qt::StringLiterals;
31
32Q_GLOBAL_STATIC(QLoggingRegistry, qtLoggingRegistry)
33alignas(QLoggingCategory) static unsigned char defaultLoggingCategory[sizeof(QLoggingCategory)];
34
35/*!
36 \internal
37 Constructs a logging rule with default values.
38*/
39QLoggingRule::QLoggingRule()
40{
41}
42
43/*!
44 \internal
45 Constructs a logging rule.
46*/
47QLoggingRule::QLoggingRule(QStringView pattern, bool enabled) : enabled(enabled)
48{
49 parse(pattern);
50}
51
52/*!
53 \internal
54 Return value 1 means filter passed, 0 means filter doesn't influence this
55 category, -1 means category doesn't pass this filter.
56 */
57int QLoggingRule::pass(QLatin1StringView cat, QtMsgType msgType) const
58{
59 // check message type
60 if (messageType > -1 && messageType != msgType)
61 return 0;
62
63 if (flags == FullText) {
64 // full match
65 if (category == cat)
66 return (enabled ? 1 : -1);
67 else
68 return 0;
69 }
70
71 const qsizetype idx = cat.indexOf(category);
72 if (idx >= 0) {
73 if (flags == MidFilter) {
74 // matches somewhere
75 return (enabled ? 1 : -1);
76 } else if (flags == LeftFilter) {
77 // matches left
78 if (idx == 0)
79 return (enabled ? 1 : -1);
80 } else if (flags == RightFilter) {
81 // matches right
82 if (idx == (cat.size() - category.size()))
83 return (enabled ? 1 : -1);
84 }
85 }
86 return 0;
87}
88
89/*!
90 \internal
91 Parses \a pattern.
92 Allowed is f.ex.:
93 qt.core.io.debug FullText, QtDebugMsg
94 qt.core.* LeftFilter, all types
95 *.io.warning RightFilter, QtWarningMsg
96 *.core.* MidFilter
97 */
98void QLoggingRule::parse(QStringView pattern)
99{
100 QStringView p;
101
102 // strip trailing ".messagetype"
103 if (pattern.endsWith(".debug"_L1)) {
104 p = pattern.chopped(6); // strlen(".debug")
105 messageType = QtDebugMsg;
106 } else if (pattern.endsWith(".info"_L1)) {
107 p = pattern.chopped(5); // strlen(".info")
108 messageType = QtInfoMsg;
109 } else if (pattern.endsWith(".warning"_L1)) {
110 p = pattern.chopped(8); // strlen(".warning")
111 messageType = QtWarningMsg;
112 } else if (pattern.endsWith(".critical"_L1)) {
113 p = pattern.chopped(9); // strlen(".critical")
114 messageType = QtCriticalMsg;
115 } else {
116 p = pattern;
117 }
118
119 const QChar asterisk = u'*';
120 if (!p.contains(asterisk)) {
121 flags = FullText;
122 } else {
123 if (p.endsWith(asterisk)) {
124 flags |= LeftFilter;
125 p = p.chopped(1);
126 }
127 if (p.startsWith(asterisk)) {
128 flags |= RightFilter;
129 p = p.mid(1);
130 }
131 if (p.contains(asterisk)) // '*' only supported at start/end
132 flags = PatternFlags();
133 }
134
135 category = p.toString();
136}
137
138/*!
139 \class QLoggingSettingsParser
140 \since 5.3
141 \internal
142
143 Parses a .ini file with the following format:
144
145 [rules]
146 rule1=[true|false]
147 rule2=[true|false]
148 ...
149
150 [rules] is the default section, and therefore optional.
151*/
152
153/*!
154 \internal
155 Parses configuration from \a content.
156*/
157void QLoggingSettingsParser::setContent(QStringView content, char16_t separator)
158{
159 _rules.clear();
160 for (auto line : qTokenize(content, separator))
161 parseNextLine(line);
162}
163
164/*!
165 \internal
166 Parses configuration from \a stream.
167*/
168void QLoggingSettingsParser::setContent(FILE *stream)
169{
170 _rules.clear();
171
172 constexpr size_t ChunkSize = 240;
173 QByteArray buffer(ChunkSize, Qt::Uninitialized);
174 auto readline = [&](FILE *stream) {
175 // Read one line into the buffer
176
177 // fgets() always writes the terminating null into the buffer, so we'll
178 // allow it to write to the QByteArray's null (thus the off by 1).
179 Q_ASSERT(buffer.size() + 1 == int(buffer.size() + 1));
180 char *s = fgets(buffer.begin(), int(buffer.size() + 1), stream);
181 if (!s)
182 return QByteArrayView{};
183
184 qsizetype len = strlen(s);
185 while (len == buffer.size()) {
186 // need to grow the buffer
187 buffer.resizeForOverwrite(buffer.size() + ChunkSize);
188 s = fgets(buffer.end() - ChunkSize, ChunkSize + 1, stream);
189 if (!s)
190 break;
191 len += strlen(s);
192 }
193 QByteArrayView result(buffer.constBegin(), len);
194 if (result.endsWith('\n'))
195 result.chop(1);
196 return result;
197 };
198
199 QByteArrayView line;
200 while (!(line = readline(stream)).isNull())
201 parseNextLine(QString::fromUtf8(line));
202}
203
204/*!
205 \internal
206 Parses one line of the configuration file
207*/
208
209void QLoggingSettingsParser::parseNextLine(QStringView line)
210{
211 // Remove whitespace at start and end of line:
212 line = line.trimmed();
213
214 // comment
215 if (line.startsWith(u';'))
216 return;
217
218 if (line.startsWith(u'[') && line.endsWith(u']')) {
219 // new section
220 auto sectionName = line.mid(1).chopped(1).trimmed();
221 m_inRulesSection = sectionName.compare("rules"_L1, Qt::CaseInsensitive) == 0;
222 return;
223 }
224
225 if (m_inRulesSection) {
226 const qsizetype equalPos = line.indexOf(u'=');
227 if (equalPos != -1) {
228 if (line.lastIndexOf(u'=') == equalPos) {
229 const auto key = line.left(equalPos).trimmed();
230#if QT_CONFIG(settings)
231 QString tmp;
232 QSettingsPrivate::iniUnescapedKey(key.toUtf8(), tmp);
233 QStringView pattern = qToStringViewIgnoringNull(tmp);
234#else
235 QStringView pattern = key;
236#endif
237 const auto valueStr = line.mid(equalPos + 1).trimmed();
238 int value = -1;
239 if (valueStr == "true"_L1)
240 value = 1;
241 else if (valueStr == "false"_L1)
242 value = 0;
243 QLoggingRule rule(pattern, (value == 1));
244 if (rule.flags != 0 && (value != -1))
245 _rules.append(std::move(rule));
246 else
247 warnMsg("Ignoring malformed logging rule: '%s'", line.toUtf8().constData());
248 } else {
249 warnMsg("Ignoring malformed logging rule: '%s'", line.toUtf8().constData());
250 }
251 }
252 }
253}
254
255/*!
256 \internal
257 QLoggingRegistry constructor
258 */
259QLoggingRegistry::QLoggingRegistry()
260 : categoryFilter(defaultCategoryFilter)
261{
262 using U = QLoggingCategory::UnregisteredInitialization;
263 Q_ASSERT_X(!self, "QLoggingRegistry", "Singleton recreated");
264 self = this;
265
266 // can't use std::construct_at here - private constructor
267 auto cat = new (defaultLoggingCategory) QLoggingCategory(U{}, defaultCategoryName);
268 categories.emplace(cat, QtDebugMsg);
269
270#if defined(Q_OS_ANDROID) || defined(Q_OS_OHOS)
271 // Unless QCoreApplication has been constructed we can't be sure that
272 // we are on Qt's main thread. If we did allow logging here, we would
273 // potentially set Qt's main thread to Android's thread 0, which would
274 // confuse Qt later when running main().
275 if (!qApp)
276 return;
277#endif
278
279 initializeRules(); // Init on first use
280}
281
282static bool qtLoggingDebug()
283{
284 static const bool debugEnv = [] {
285 bool debug = qEnvironmentVariableIsSet("QT_LOGGING_DEBUG");
286 if (debug)
287 debugMsg("QT_LOGGING_DEBUG environment variable is set.");
288 return debug;
289 }();
290 return Q_UNLIKELY(debugEnv);
291}
292
293static QList<QLoggingRule> loadRulesFromFile(const QString &filePath)
294{
295 Q_ASSERT(!filePath.isEmpty());
296 if (qtLoggingDebug()) {
297 debugMsg("Checking \"%s\" for rules",
298 QDir::toNativeSeparators(filePath).toUtf8().constData());
299 }
300
301 // We bypass QFile here because QFile is a QObject.
302 if (Q_UNLIKELY(filePath.at(0) == u':')) {
303 if (qtLoggingDebug()) {
304 warnMsg("Attempted to load config rules from Qt resource path \"%ls\"",
305 qUtf16Printable(filePath));
306 }
307 return {};
308 }
309
310#ifdef Q_OS_WIN
311 // text mode: let the runtime do CRLF translation
312 FILE *f = _wfopen(reinterpret_cast<const wchar_t *>(filePath.constBegin()), L"rtN");
313#else
314 FILE *f = QT_FOPEN(QFile::encodeName(filePath).constBegin(), "re");
315#endif
316 if (f) {
318 parser.setContent(f);
319 fclose(f);
321 debugMsg("Loaded %td rules from \"%ls\"", static_cast<ptrdiff_t>(parser.rules().size()),
322 qUtf16Printable(filePath));
323 return parser.rules();
324 } else if (int err = errno; err != ENOENT) {
325 warnMsg("Failed to load file \"%ls\": %ls", qUtf16Printable(filePath),
326 qUtf16Printable(qt_error_string(err)));
327 }
328 return QList<QLoggingRule>();
329}
330
331/*!
332 \internal
333 Initializes the rules database by loading
334 $QT_LOGGING_CONF, $QT_LOGGING_RULES, and .config/QtProject/qtlogging.ini.
335 */
337{
338 if (qtLoggingDebug()) {
339 debugMsg("Initializing the rules database ...");
340 debugMsg("Checking %s environment variable", "QT_LOGGING_CONF");
341 }
342 QList<QLoggingRule> er, qr, cr;
343 // get rules from environment
344 if (QString rulesFilePath = qEnvironmentVariable("QT_LOGGING_CONF"); !rulesFilePath.isEmpty())
345 er = loadRulesFromFile(rulesFilePath);
346
348 debugMsg("Checking %s environment variable", "QT_LOGGING_RULES");
349
350 const QString rulesSrc = qEnvironmentVariable("QT_LOGGING_RULES");
351 if (!rulesSrc.isEmpty()) {
353 parser.setImplicitRulesSection(true);
354 parser.setContent(rulesSrc, u';');
355
357 debugMsg("Loaded %td rules", static_cast<ptrdiff_t>(parser.rules().size()));
358
359 er += parser.rules();
360 }
361
362 const QString configFileName = u"QtProject/qtlogging.ini"_s;
363 QStringView baseConfigFileName = QStringView(configFileName).sliced(strlen("QtProject"));
364 Q_ASSERT(baseConfigFileName.startsWith(u'/'));
365
366 // get rules from Qt data configuration path
367 {
368 const QStringList dataPaths = QLibraryInfo::paths(QLibraryInfo::DataPath);
369 for (const QString &dataPath : dataPaths)
370 qr += loadRulesFromFile(dataPath + baseConfigFileName);
371 }
372
373 // get rules from user's/system configuration
374 // locateAll() returns the user's file (most overriding) first
375 const QStringList configPaths =
376 QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, configFileName);
377 for (qsizetype i = configPaths.size(); i > 0; --i)
378 cr += loadRulesFromFile(configPaths[i - 1]);
379
380 const QMutexLocker locker(&registryMutex);
381
382 ruleSets[EnvironmentRules] = std::move(er);
383 ruleSets[QtConfigRules] = std::move(qr);
384 ruleSets[ConfigRules] = std::move(cr);
385
386 if (!ruleSets[EnvironmentRules].isEmpty() || !ruleSets[QtConfigRules].isEmpty() || !ruleSets[ConfigRules].isEmpty())
387 updateRules();
388}
389
390/*!
391 \internal
392 Registers a category object.
393
394 This method might be called concurrently for the same category object.
395*/
396void QLoggingRegistry::registerCategory(QLoggingCategory *cat, QtMsgType enableForLevel)
397{
398 const auto locker = qt_scoped_lock(registryMutex);
399
400 auto r = categories.tryEmplace(cat, enableForLevel);
401 if (r.inserted) {
402 // new entry
403 (*categoryFilter)(cat);
404 }
405}
406
407/*!
408 \internal
409 Unregisters a category object.
410*/
411void QLoggingRegistry::unregisterCategory(QLoggingCategory *cat)
412{
413 const auto locker = qt_scoped_lock(registryMutex);
414 categories.remove(cat);
415}
416
417/*!
418 \since 6.3
419 \internal
420
421 Registers the environment variable \a environment as the control variable
422 for enabling debugging by default for category \a categoryName. The
423 category name must start with "qt."
424*/
426 const char *environment)
427{
428 qtCategoryEnvironmentOverrides.insert_or_assign(categoryName, environment);
429}
430
431/*!
432 \internal
433 Installs logging rules as specified in \a content.
434 */
435void QLoggingRegistry::setApiRules(const QString &content)
436{
438 parser.setImplicitRulesSection(true);
439 parser.setContent(content);
440
442 debugMsg("Loading logging rules set by QLoggingCategory::setFilterRules ...");
443
444 const QMutexLocker locker(&registryMutex);
445
446 ruleSets[ApiRules] = parser.rules();
447
448 updateRules();
449}
450
451/*!
452 \internal
453 Activates a new set of logging rules for the default filter.
454
455 (The caller must lock registryMutex to make sure the API is thread safe.)
456*/
457void QLoggingRegistry::updateRules()
458{
459 for (auto it = categories.keyBegin(), end = categories.keyEnd(); it != end; ++it)
460 (*categoryFilter)(*it);
461}
462
463/*!
464 \internal
465 Installs a custom filter rule.
466*/
467QLoggingCategory::CategoryFilter
468QLoggingRegistry::installFilter(QLoggingCategory::CategoryFilter filter)
469{
470 const auto locker = qt_scoped_lock(registryMutex);
471
472 if (!filter)
473 filter = defaultCategoryFilter;
474
475 QLoggingCategory::CategoryFilter old = categoryFilter;
476 categoryFilter = filter;
477
478 updateRules();
479
480 return old;
481}
482
484{
485 Q_CONSTINIT thread_local bool recursionGuard = false;
486 if (recursionGuard)
487 return nullptr;
488 QScopedValueRollback<bool> rollback(recursionGuard, true);
489 return qtLoggingRegistry();
490}
491
492QLoggingCategory *QLoggingRegistry::defaultCategory()
493{
494 // Initialize the defaultLoggingCategory global static, if necessary. Note
495 // how it remains initialized forever, even if the QLoggingRegistry
496 // instance() is destroyed.
497 instance();
498
499 // std::launder() to be on the safe side, but it's unnecessary because the
500 // object is never recreated.
501 return std::launder(reinterpret_cast<QLoggingCategory *>(defaultLoggingCategory));
502}
503
504/*!
505 \internal
506 Updates category settings according to rules.
507
508 As a category filter, it is run with registryMutex held.
509*/
510void QLoggingRegistry::defaultCategoryFilter(QLoggingCategory *cat)
511{
512 const QLoggingRegistry *reg = self;
513 Q_ASSERT(reg->categories.contains(cat));
514 QtMsgType enableForLevel = reg->categories.value(cat);
515
516 // NB: note that the numeric values of the Qt*Msg constants are
517 // not in severity order.
518 bool debug = (enableForLevel == QtDebugMsg);
519 bool info = debug || (enableForLevel == QtInfoMsg);
520 bool warning = info || (enableForLevel == QtWarningMsg);
521 bool critical = warning || (enableForLevel == QtCriticalMsg);
522
523 // hard-wired implementation of
524 // qt.*.debug=false
525 // qt.debug=false
526 if (const char *categoryName = cat->categoryName()) {
527 // == "qt" or startsWith("qt.")
528 if (strcmp(categoryName, "qt") == 0) {
529 debug = false;
530 } else if (strncmp(categoryName, "qt.", 3) == 0) {
531 // may be overridden
532 auto it = reg->qtCategoryEnvironmentOverrides.find(categoryName);
533 if (it == reg->qtCategoryEnvironmentOverrides.end())
534 debug = false;
535 else
536 debug = qEnvironmentVariableIntValue(it->second);
537 }
538 }
539
540 const auto categoryName = QLatin1StringView(cat->categoryName());
541
542 for (const auto &ruleSet : reg->ruleSets) {
543 for (const auto &rule : ruleSet) {
544 int filterpass = rule.pass(categoryName, QtDebugMsg);
545 if (filterpass != 0)
546 debug = (filterpass > 0);
547 filterpass = rule.pass(categoryName, QtInfoMsg);
548 if (filterpass != 0)
549 info = (filterpass > 0);
550 filterpass = rule.pass(categoryName, QtWarningMsg);
551 if (filterpass != 0)
552 warning = (filterpass > 0);
553 filterpass = rule.pass(categoryName, QtCriticalMsg);
554 if (filterpass != 0)
555 critical = (filterpass > 0);
556 }
557 }
558
559 cat->setEnabled(QtDebugMsg, debug);
560 cat->setEnabled(QtInfoMsg, info);
561 cat->setEnabled(QtWarningMsg, warning);
562 cat->setEnabled(QtCriticalMsg, critical);
563
564 for (const auto &ruleSet : reg->ruleSets) {
565 for (const auto &rule : ruleSet) {
566 // this must be an exact match
567 if (rule.messageType != QtDebugMsg && rule.flags != QLoggingRule::FullText)
568 continue;
569 if (rule.category != "_logging_categories"_L1)
570 continue;
571 if (rule.enabled) {
572 registryMsg("CATEGORY:%s %d %d %d %d",
573 cat->categoryName(),
574 cat->isDebugEnabled(),
575 cat->isWarningEnabled(),
576 cat->isCriticalEnabled(),
577 cat->isInfoEnabled()
578 );
579 }
580 break;
581 }
582 }
583}
584
585
586QT_END_NAMESPACE
void unregisterCategory(QLoggingCategory *category)
Q_CORE_EXPORT void registerEnvironmentOverrideForCategory(const char *categoryName, const char *environment)
Q_AUTOTEST_EXPORT void initializeRules()
void registerCategory(QLoggingCategory *category, QtMsgType enableForLevel)
void setApiRules(const QString &content)
Combined button and popup list for selecting options.
static QList< QLoggingRule > loadRulesFromFile(const QString &filePath)
static bool qtLoggingDebug()
#define warnMsg
#define debugMsg
#define registryMsg
static unsigned char defaultLoggingCategory[sizeof(QLoggingCategory)]