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

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