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)
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);
320 if (qtLoggingDebug())
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
347 if (qtLoggingDebug())
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
356 if (qtLoggingDebug())
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 qr = loadRulesFromFile(QLibraryInfo::path(QLibraryInfo::DataPath) + baseConfigFileName);
368
369 // get rules from user's/system configuration
370 // locateAll() returns the user's file (most overriding) first
371 const QStringList configPaths =
372 QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, configFileName);
373 for (qsizetype i = configPaths.size(); i > 0; --i)
374 cr += loadRulesFromFile(configPaths[i - 1]);
375
376 const QMutexLocker locker(&registryMutex);
377
378 ruleSets[EnvironmentRules] = std::move(er);
379 ruleSets[QtConfigRules] = std::move(qr);
380 ruleSets[ConfigRules] = std::move(cr);
381
382 if (!ruleSets[EnvironmentRules].isEmpty() || !ruleSets[QtConfigRules].isEmpty() || !ruleSets[ConfigRules].isEmpty())
383 updateRules();
384}
385
386/*!
387 \internal
388 Registers a category object.
389
390 This method might be called concurrently for the same category object.
391*/
392void QLoggingRegistry::registerCategory(QLoggingCategory *cat, QtMsgType enableForLevel)
393{
394 const auto locker = qt_scoped_lock(registryMutex);
395
396 auto r = categories.tryEmplace(cat, enableForLevel);
397 if (r.inserted) {
398 // new entry
399 (*categoryFilter)(cat);
400 }
401}
402
403/*!
404 \internal
405 Unregisters a category object.
406*/
407void QLoggingRegistry::unregisterCategory(QLoggingCategory *cat)
408{
409 const auto locker = qt_scoped_lock(registryMutex);
410 categories.remove(cat);
411}
412
413/*!
414 \since 6.3
415 \internal
416
417 Registers the environment variable \a environment as the control variable
418 for enabling debugging by default for category \a categoryName. The
419 category name must start with "qt."
420*/
422 const char *environment)
423{
424 qtCategoryEnvironmentOverrides.insert_or_assign(categoryName, environment);
425}
426
427/*!
428 \internal
429 Installs logging rules as specified in \a content.
430 */
431void QLoggingRegistry::setApiRules(const QString &content)
432{
434 parser.setImplicitRulesSection(true);
435 parser.setContent(content);
436
437 if (qtLoggingDebug())
438 debugMsg("Loading logging rules set by QLoggingCategory::setFilterRules ...");
439
440 const QMutexLocker locker(&registryMutex);
441
442 ruleSets[ApiRules] = parser.rules();
443
444 updateRules();
445}
446
447/*!
448 \internal
449 Activates a new set of logging rules for the default filter.
450
451 (The caller must lock registryMutex to make sure the API is thread safe.)
452*/
453void QLoggingRegistry::updateRules()
454{
455 for (auto it = categories.keyBegin(), end = categories.keyEnd(); it != end; ++it)
456 (*categoryFilter)(*it);
457}
458
459/*!
460 \internal
461 Installs a custom filter rule.
462*/
463QLoggingCategory::CategoryFilter
464QLoggingRegistry::installFilter(QLoggingCategory::CategoryFilter filter)
465{
466 const auto locker = qt_scoped_lock(registryMutex);
467
468 if (!filter)
469 filter = defaultCategoryFilter;
470
471 QLoggingCategory::CategoryFilter old = categoryFilter;
472 categoryFilter = filter;
473
474 updateRules();
475
476 return old;
477}
478
480{
481 Q_CONSTINIT thread_local bool recursionGuard = false;
482 if (recursionGuard)
483 return nullptr;
484 QScopedValueRollback<bool> rollback(recursionGuard, true);
485 return qtLoggingRegistry();
486}
487
488QLoggingCategory *QLoggingRegistry::defaultCategory()
489{
490 // Initialize the defaultLoggingCategory global static, if necessary. Note
491 // how it remains initialized forever, even if the QLoggingRegistry
492 // instance() is destroyed.
493 instance();
494
495 // std::launder() to be on the safe side, but it's unnecessary because the
496 // object is never recreated.
497 return std::launder(reinterpret_cast<QLoggingCategory *>(defaultLoggingCategory));
498}
499
500/*!
501 \internal
502 Updates category settings according to rules.
503
504 As a category filter, it is run with registryMutex held.
505*/
506void QLoggingRegistry::defaultCategoryFilter(QLoggingCategory *cat)
507{
508 const QLoggingRegistry *reg = self;
509 Q_ASSERT(reg->categories.contains(cat));
510 QtMsgType enableForLevel = reg->categories.value(cat);
511
512 // NB: note that the numeric values of the Qt*Msg constants are
513 // not in severity order.
514 bool debug = (enableForLevel == QtDebugMsg);
515 bool info = debug || (enableForLevel == QtInfoMsg);
516 bool warning = info || (enableForLevel == QtWarningMsg);
517 bool critical = warning || (enableForLevel == QtCriticalMsg);
518
519 // hard-wired implementation of
520 // qt.*.debug=false
521 // qt.debug=false
522 if (const char *categoryName = cat->categoryName()) {
523 // == "qt" or startsWith("qt.")
524 if (strcmp(categoryName, "qt") == 0) {
525 debug = false;
526 } else if (strncmp(categoryName, "qt.", 3) == 0) {
527 // may be overridden
528 auto it = reg->qtCategoryEnvironmentOverrides.find(categoryName);
529 if (it == reg->qtCategoryEnvironmentOverrides.end())
530 debug = false;
531 else
532 debug = qEnvironmentVariableIntValue(it->second);
533 }
534 }
535
536 const auto categoryName = QLatin1StringView(cat->categoryName());
537
538 for (const auto &ruleSet : reg->ruleSets) {
539 for (const auto &rule : ruleSet) {
540 int filterpass = rule.pass(categoryName, QtDebugMsg);
541 if (filterpass != 0)
542 debug = (filterpass > 0);
543 filterpass = rule.pass(categoryName, QtInfoMsg);
544 if (filterpass != 0)
545 info = (filterpass > 0);
546 filterpass = rule.pass(categoryName, QtWarningMsg);
547 if (filterpass != 0)
548 warning = (filterpass > 0);
549 filterpass = rule.pass(categoryName, QtCriticalMsg);
550 if (filterpass != 0)
551 critical = (filterpass > 0);
552 }
553 }
554
555 cat->setEnabled(QtDebugMsg, debug);
556 cat->setEnabled(QtInfoMsg, info);
557 cat->setEnabled(QtWarningMsg, warning);
558 cat->setEnabled(QtCriticalMsg, critical);
559
560 for (const auto &ruleSet : reg->ruleSets) {
561 for (const auto &rule : ruleSet) {
562 // this must be an exact match
563 if (rule.messageType != QtDebugMsg && rule.flags != QLoggingRule::FullText)
564 continue;
565 if (rule.category != "_logging_categories"_L1)
566 continue;
567 if (rule.enabled) {
568 registryMsg("CATEGORY:%s %d %d %d %d",
569 cat->categoryName(),
570 cat->isDebugEnabled(),
571 cat->isWarningEnabled(),
572 cat->isCriticalEnabled(),
573 cat->isInfoEnabled()
574 );
575 }
576 break;
577 }
578 }
579}
580
581
582QT_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)
static QList< QLoggingRule > loadRulesFromFile(const QString &filePath)
static bool qtLoggingDebug()
#define warnMsg
#define debugMsg
#define registryMsg
static unsigned char defaultLoggingCategory[sizeof(QLoggingCategory)]