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
4#include "qloggingregistry_p.h"
5
6#include <QtCore/qfile.h>
7#include <QtCore/qlibraryinfo.h>
8#include <QtCore/private/qlocking_p.h>
9#include <QtCore/qstandardpaths.h>
10#include <QtCore/qstringtokenizer.h>
11#include <QtCore/qtextstream.h>
12#include <QtCore/qdir.h>
13#include <QtCore/qcoreapplication.h>
14
15#if QT_CONFIG(settings)
16#include <QtCore/qsettings.h>
17#include <QtCore/private/qsettings_p.h>
18#endif
19
20// We can't use the default macros because this would lead to recursion.
21// Instead let's define our own one that unconditionally logs...
22#define debugMsg QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC, "qt.core.logging").debug
23#define warnMsg QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC, "qt.core.logging").warning
24
25QT_BEGIN_NAMESPACE
26
27using namespace Qt::StringLiterals;
28
29Q_GLOBAL_STATIC(QLoggingRegistry, qtLoggingRegistry)
30
31/*!
32 \internal
33 Constructs a logging rule with default values.
34*/
35QLoggingRule::QLoggingRule()
36{
37}
38
39/*!
40 \internal
41 Constructs a logging rule.
42*/
43QLoggingRule::QLoggingRule(QStringView pattern, bool enabled) : enabled(enabled)
44{
45 parse(pattern);
46}
47
48/*!
49 \internal
50 Return value 1 means filter passed, 0 means filter doesn't influence this
51 category, -1 means category doesn't pass this filter.
52 */
53int QLoggingRule::pass(QLatin1StringView cat, QtMsgType msgType) const
54{
55 // check message type
56 if (messageType > -1 && messageType != msgType)
57 return 0;
58
59 if (flags == FullText) {
60 // full match
61 if (category == cat)
62 return (enabled ? 1 : -1);
63 else
64 return 0;
65 }
66
67 const qsizetype idx = cat.indexOf(s: category);
68 if (idx >= 0) {
69 if (flags == MidFilter) {
70 // matches somewhere
71 return (enabled ? 1 : -1);
72 } else if (flags == LeftFilter) {
73 // matches left
74 if (idx == 0)
75 return (enabled ? 1 : -1);
76 } else if (flags == RightFilter) {
77 // matches right
78 if (idx == (cat.size() - category.size()))
79 return (enabled ? 1 : -1);
80 }
81 }
82 return 0;
83}
84
85/*!
86 \internal
87 Parses \a pattern.
88 Allowed is f.ex.:
89 qt.core.io.debug FullText, QtDebugMsg
90 qt.core.* LeftFilter, all types
91 *.io.warning RightFilter, QtWarningMsg
92 *.core.* MidFilter
93 */
94void QLoggingRule::parse(QStringView pattern)
95{
96 QStringView p;
97
98 // strip trailing ".messagetype"
99 if (pattern.endsWith(s: ".debug"_L1)) {
100 p = pattern.chopped(n: 6); // strlen(".debug")
101 messageType = QtDebugMsg;
102 } else if (pattern.endsWith(s: ".info"_L1)) {
103 p = pattern.chopped(n: 5); // strlen(".info")
104 messageType = QtInfoMsg;
105 } else if (pattern.endsWith(s: ".warning"_L1)) {
106 p = pattern.chopped(n: 8); // strlen(".warning")
107 messageType = QtWarningMsg;
108 } else if (pattern.endsWith(s: ".critical"_L1)) {
109 p = pattern.chopped(n: 9); // strlen(".critical")
110 messageType = QtCriticalMsg;
111 } else {
112 p = pattern;
113 }
114
115 const QChar asterisk = u'*';
116 if (!p.contains(c: asterisk)) {
117 flags = FullText;
118 } else {
119 if (p.endsWith(c: asterisk)) {
120 flags |= LeftFilter;
121 p = p.chopped(n: 1);
122 }
123 if (p.startsWith(c: asterisk)) {
124 flags |= RightFilter;
125 p = p.mid(pos: 1);
126 }
127 if (p.contains(c: asterisk)) // '*' only supported at start/end
128 flags = PatternFlags();
129 }
130
131 category = p.toString();
132}
133
134/*!
135 \class QLoggingSettingsParser
136 \since 5.3
137 \internal
138
139 Parses a .ini file with the following format:
140
141 [rules]
142 rule1=[true|false]
143 rule2=[true|false]
144 ...
145
146 [rules] is the default section, and therefore optional.
147*/
148
149/*!
150 \internal
151 Parses configuration from \a content.
152*/
153void QLoggingSettingsParser::setContent(QStringView content)
154{
155 _rules.clear();
156 for (auto line : qTokenize(h&: content, n: u'\n'))
157 parseNextLine(line);
158}
159
160/*!
161 \internal
162 Parses configuration from \a stream.
163*/
164void QLoggingSettingsParser::setContent(QTextStream &stream)
165{
166 _rules.clear();
167 QString line;
168 while (stream.readLineInto(line: &line))
169 parseNextLine(line: qToStringViewIgnoringNull(s: line));
170}
171
172/*!
173 \internal
174 Parses one line of the configuration file
175*/
176
177void QLoggingSettingsParser::parseNextLine(QStringView line)
178{
179 // Remove whitespace at start and end of line:
180 line = line.trimmed();
181
182 // comment
183 if (line.startsWith(c: u';'))
184 return;
185
186 if (line.startsWith(c: u'[') && line.endsWith(c: u']')) {
187 // new section
188 auto sectionName = line.mid(pos: 1).chopped(n: 1).trimmed();
189 m_inRulesSection = sectionName.compare(s: "rules"_L1, cs: Qt::CaseInsensitive) == 0;
190 return;
191 }
192
193 if (m_inRulesSection) {
194 const qsizetype equalPos = line.indexOf(c: u'=');
195 if (equalPos != -1) {
196 if (line.lastIndexOf(c: u'=') == equalPos) {
197 const auto key = line.left(n: equalPos).trimmed();
198#if QT_CONFIG(settings)
199 QString tmp;
200 QSettingsPrivate::iniUnescapedKey(key: key.toUtf8(), result&: tmp);
201 QStringView pattern = qToStringViewIgnoringNull(s: tmp);
202#else
203 QStringView pattern = key;
204#endif
205 const auto valueStr = line.mid(pos: equalPos + 1).trimmed();
206 int value = -1;
207 if (valueStr == "true"_L1)
208 value = 1;
209 else if (valueStr == "false"_L1)
210 value = 0;
211 QLoggingRule rule(pattern, (value == 1));
212 if (rule.flags != 0 && (value != -1))
213 _rules.append(t: std::move(rule));
214 else
215 warnMsg(msg: "Ignoring malformed logging rule: '%s'", line.toUtf8().constData());
216 } else {
217 warnMsg(msg: "Ignoring malformed logging rule: '%s'", line.toUtf8().constData());
218 }
219 }
220 }
221}
222
223/*!
224 \internal
225 QLoggingRegistry constructor
226 */
227QLoggingRegistry::QLoggingRegistry()
228 : categoryFilter(defaultCategoryFilter)
229{
230#if defined(Q_OS_ANDROID)
231 // Unless QCoreApplication has been constructed we can't be sure that
232 // we are on Qt's main thread. If we did allow logging here, we would
233 // potentially set Qt's main thread to Android's thread 0, which would
234 // confuse Qt later when running main().
235 if (!qApp)
236 return;
237#endif
238
239 initializeRules(); // Init on first use
240}
241
242static bool qtLoggingDebug()
243{
244 static const bool debugEnv = [] {
245 bool debug = qEnvironmentVariableIsSet(varName: "QT_LOGGING_DEBUG");
246 if (debug)
247 debugMsg(msg: "QT_LOGGING_DEBUG environment variable is set.");
248 return debug;
249 }();
250 return debugEnv;
251}
252
253static QList<QLoggingRule> loadRulesFromFile(const QString &filePath)
254{
255 if (qtLoggingDebug()) {
256 debugMsg(msg: "Checking \"%s\" for rules",
257 QDir::toNativeSeparators(pathName: filePath).toUtf8().constData());
258 }
259
260 QFile file(filePath);
261 if (file.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
262 QTextStream stream(&file);
263 QLoggingSettingsParser parser;
264 parser.setContent(stream);
265 if (qtLoggingDebug())
266 debugMsg(msg: "Loaded %td rules", static_cast<ptrdiff_t>(parser.rules().size()));
267 return parser.rules();
268 }
269 return QList<QLoggingRule>();
270}
271
272/*!
273 \internal
274 Initializes the rules database by loading
275 $QT_LOGGING_CONF, $QT_LOGGING_RULES, and .config/QtProject/qtlogging.ini.
276 */
277void QLoggingRegistry::initializeRules()
278{
279 if (qtLoggingDebug()) {
280 debugMsg(msg: "Initializing the rules database ...");
281 debugMsg(msg: "Checking %s environment variable", "QT_LOGGING_CONF");
282 }
283 QList<QLoggingRule> er, qr, cr;
284 // get rules from environment
285 const QByteArray rulesFilePath = qgetenv(varName: "QT_LOGGING_CONF");
286 if (!rulesFilePath.isEmpty())
287 er = loadRulesFromFile(filePath: QFile::decodeName(localFileName: rulesFilePath));
288
289 if (qtLoggingDebug())
290 debugMsg(msg: "Checking %s environment variable", "QT_LOGGING_RULES");
291
292 const QByteArray rulesSrc = qgetenv(varName: "QT_LOGGING_RULES").replace(before: ';', after: '\n');
293 if (!rulesSrc.isEmpty()) {
294 QTextStream stream(rulesSrc);
295 QLoggingSettingsParser parser;
296 parser.setImplicitRulesSection(true);
297 parser.setContent(stream);
298
299 if (qtLoggingDebug())
300 debugMsg(msg: "Loaded %td rules", static_cast<ptrdiff_t>(parser.rules().size()));
301
302 er += parser.rules();
303 }
304
305 const QString configFileName = QStringLiteral("qtlogging.ini");
306
307#if !defined(QT_BOOTSTRAPPED)
308 // get rules from Qt data configuration path
309 const QString qtConfigPath
310 = QDir(QLibraryInfo::path(p: QLibraryInfo::DataPath)).absoluteFilePath(fileName: configFileName);
311 qr = loadRulesFromFile(filePath: qtConfigPath);
312#endif
313
314 // get rules from user's/system configuration
315 const QString envPath = QStandardPaths::locate(type: QStandardPaths::GenericConfigLocation,
316 fileName: QString::fromLatin1(ba: "QtProject/") + configFileName);
317 if (!envPath.isEmpty())
318 cr = loadRulesFromFile(filePath: envPath);
319
320 const QMutexLocker locker(&registryMutex);
321
322 ruleSets[EnvironmentRules] = std::move(er);
323 ruleSets[QtConfigRules] = std::move(qr);
324 ruleSets[ConfigRules] = std::move(cr);
325
326 if (!ruleSets[EnvironmentRules].isEmpty() || !ruleSets[QtConfigRules].isEmpty() || !ruleSets[ConfigRules].isEmpty())
327 updateRules();
328}
329
330/*!
331 \internal
332 Registers a category object.
333
334 This method might be called concurrently for the same category object.
335*/
336void QLoggingRegistry::registerCategory(QLoggingCategory *cat, QtMsgType enableForLevel)
337{
338 const auto locker = qt_scoped_lock(mutex&: registryMutex);
339
340 const auto oldSize = categories.size();
341 auto &e = categories[cat];
342 if (categories.size() != oldSize) {
343 // new entry
344 e = enableForLevel;
345 (*categoryFilter)(cat);
346 }
347}
348
349/*!
350 \internal
351 Unregisters a category object.
352*/
353void QLoggingRegistry::unregisterCategory(QLoggingCategory *cat)
354{
355 const auto locker = qt_scoped_lock(mutex&: registryMutex);
356 categories.remove(key: cat);
357}
358
359/*!
360 \since 6.3
361 \internal
362
363 Registers the environment variable \a environment as the control variable
364 for enabling debugging by default for category \a categoryName. The
365 category name must start with "qt."
366*/
367void QLoggingRegistry::registerEnvironmentOverrideForCategory(const char *categoryName,
368 const char *environment)
369{
370 qtCategoryEnvironmentOverrides.insert_or_assign(k: categoryName, obj&: environment);
371}
372
373/*!
374 \internal
375 Installs logging rules as specified in \a content.
376 */
377void QLoggingRegistry::setApiRules(const QString &content)
378{
379 QLoggingSettingsParser parser;
380 parser.setImplicitRulesSection(true);
381 parser.setContent(content);
382
383 if (qtLoggingDebug())
384 debugMsg(msg: "Loading logging rules set by QLoggingCategory::setFilterRules ...");
385
386 const QMutexLocker locker(&registryMutex);
387
388 ruleSets[ApiRules] = parser.rules();
389
390 updateRules();
391}
392
393/*!
394 \internal
395 Activates a new set of logging rules for the default filter.
396
397 (The caller must lock registryMutex to make sure the API is thread safe.)
398*/
399void QLoggingRegistry::updateRules()
400{
401 for (auto it = categories.keyBegin(), end = categories.keyEnd(); it != end; ++it)
402 (*categoryFilter)(*it);
403}
404
405/*!
406 \internal
407 Installs a custom filter rule.
408*/
409QLoggingCategory::CategoryFilter
410QLoggingRegistry::installFilter(QLoggingCategory::CategoryFilter filter)
411{
412 const auto locker = qt_scoped_lock(mutex&: registryMutex);
413
414 if (!filter)
415 filter = defaultCategoryFilter;
416
417 QLoggingCategory::CategoryFilter old = categoryFilter;
418 categoryFilter = filter;
419
420 updateRules();
421
422 return old;
423}
424
425QLoggingRegistry *QLoggingRegistry::instance()
426{
427 return qtLoggingRegistry();
428}
429
430/*!
431 \internal
432 Updates category settings according to rules.
433
434 As a category filter, it is run with registryMutex held.
435*/
436void QLoggingRegistry::defaultCategoryFilter(QLoggingCategory *cat)
437{
438 const QLoggingRegistry *reg = QLoggingRegistry::instance();
439 Q_ASSERT(reg->categories.contains(cat));
440 QtMsgType enableForLevel = reg->categories.value(key: cat);
441
442 // NB: note that the numeric values of the Qt*Msg constants are
443 // not in severity order.
444 bool debug = (enableForLevel == QtDebugMsg);
445 bool info = debug || (enableForLevel == QtInfoMsg);
446 bool warning = info || (enableForLevel == QtWarningMsg);
447 bool critical = warning || (enableForLevel == QtCriticalMsg);
448
449 // hard-wired implementation of
450 // qt.*.debug=false
451 // qt.debug=false
452 if (const char *categoryName = cat->categoryName()) {
453 // == "qt" or startsWith("qt.")
454 if (strcmp(s1: categoryName, s2: "qt") == 0) {
455 debug = false;
456 } else if (strncmp(s1: categoryName, s2: "qt.", n: 3) == 0) {
457 // may be overridden
458 auto it = reg->qtCategoryEnvironmentOverrides.find(x: categoryName);
459 if (it == reg->qtCategoryEnvironmentOverrides.end())
460 debug = false;
461 else
462 debug = qEnvironmentVariableIntValue(varName: it->second);
463 }
464 }
465
466 const auto categoryName = QLatin1StringView(cat->categoryName());
467
468 for (const auto &ruleSet : reg->ruleSets) {
469 for (const auto &rule : ruleSet) {
470 int filterpass = rule.pass(cat: categoryName, msgType: QtDebugMsg);
471 if (filterpass != 0)
472 debug = (filterpass > 0);
473 filterpass = rule.pass(cat: categoryName, msgType: QtInfoMsg);
474 if (filterpass != 0)
475 info = (filterpass > 0);
476 filterpass = rule.pass(cat: categoryName, msgType: QtWarningMsg);
477 if (filterpass != 0)
478 warning = (filterpass > 0);
479 filterpass = rule.pass(cat: categoryName, msgType: QtCriticalMsg);
480 if (filterpass != 0)
481 critical = (filterpass > 0);
482 }
483 }
484
485 cat->setEnabled(type: QtDebugMsg, enable: debug);
486 cat->setEnabled(type: QtInfoMsg, enable: info);
487 cat->setEnabled(type: QtWarningMsg, enable: warning);
488 cat->setEnabled(type: QtCriticalMsg, enable: critical);
489}
490
491
492QT_END_NAMESPACE
493

source code of qtbase/src/corelib/io/qloggingregistry.cpp