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(category);
105 if (idx >= 0) {
106 if (flags == MidFilter) {
107 // matches somewhere
108 return (enabled ? 1 : -1);
109 } else if (flags == LeftFilter) {
110 // matches left
111 if (idx == 0)
112 return (enabled ? 1 : -1);
113 } else if (flags == RightFilter) {
114 // matches right
115 if (idx == (cat.size() - category.count()))
116 return (enabled ? 1 : -1);
117 }
118 }
119 return 0;
120}
121
122/*!
123 \internal
124 Parses \a pattern.
125 Allowed is f.ex.:
126 qt.core.io.debug FullText, QtDebugMsg
127 qt.core.* LeftFilter, all types
128 *.io.warning RightFilter, QtWarningMsg
129 *.core.* MidFilter
130 */
131void QLoggingRule::parse(QStringView pattern)
132{
133 QStringView p;
134
135 // strip trailing ".messagetype"
136 if (pattern.endsWith(QLatin1String(".debug"))) {
137 p = pattern.chopped(6); // strlen(".debug")
138 messageType = QtDebugMsg;
139 } else if (pattern.endsWith(QLatin1String(".info"))) {
140 p = pattern.chopped(5); // strlen(".info")
141 messageType = QtInfoMsg;
142 } else if (pattern.endsWith(QLatin1String(".warning"))) {
143 p = pattern.chopped(8); // strlen(".warning")
144 messageType = QtWarningMsg;
145 } else if (pattern.endsWith(QLatin1String(".critical"))) {
146 p = pattern.chopped(9); // strlen(".critical")
147 messageType = QtCriticalMsg;
148 } else {
149 p = pattern;
150 }
151
152 if (!p.contains(QLatin1Char('*'))) {
153 flags = FullText;
154 } else {
155 if (p.endsWith(QLatin1Char('*'))) {
156 flags |= LeftFilter;
157 p = p.chopped(1);
158 }
159 if (p.startsWith(QLatin1Char('*'))) {
160 flags |= RightFilter;
161 p = p.mid(1);
162 }
163 if (p.contains(QLatin1Char('*'))) // '*' only supported at start/end
164 flags = PatternFlags();
165 }
166
167 category = p.toString();
168}
169
170/*!
171 \class QLoggingSettingsParser
172 \since 5.3
173 \internal
174
175 Parses a .ini file with the following format:
176
177 [rules]
178 rule1=[true|false]
179 rule2=[true|false]
180 ...
181
182 [rules] is the default section, and therefore optional.
183*/
184
185/*!
186 \internal
187 Parses configuration from \a content.
188*/
189void QLoggingSettingsParser::setContent(const QString &content)
190{
191 _rules.clear();
192 const auto lines = QStringView{content}.split(QLatin1Char('\n'));
193 for (const auto &line : lines)
194 parseNextLine(line);
195}
196
197/*!
198 \internal
199 Parses configuration from \a stream.
200*/
201void QLoggingSettingsParser::setContent(QTextStream &stream)
202{
203 _rules.clear();
204 QString line;
205 while (stream.readLineInto(&line))
206 parseNextLine(qToStringViewIgnoringNull(line));
207}
208
209/*!
210 \internal
211 Parses one line of the configuation file
212*/
213
214void QLoggingSettingsParser::parseNextLine(QStringView line)
215{
216 // Remove whitespace at start and end of line:
217 line = line.trimmed();
218
219 // comment
220 if (line.startsWith(QLatin1Char(';')))
221 return;
222
223 if (line.startsWith(QLatin1Char('[')) && line.endsWith(QLatin1Char(']'))) {
224 // new section
225 auto sectionName = line.mid(1).chopped(1).trimmed();
226 m_inRulesSection = sectionName.compare(QLatin1String("rules"), Qt::CaseInsensitive) == 0;
227 return;
228 }
229
230 if (m_inRulesSection) {
231 int equalPos = line.indexOf(QLatin1Char('='));
232 if (equalPos != -1) {
233 if (line.lastIndexOf(QLatin1Char('=')) == equalPos) {
234 const auto key = line.left(equalPos).trimmed();
235#if QT_CONFIG(settings)
236 QString tmp;
237 QSettingsPrivate::iniUnescapedKey(key.toUtf8(), 0, key.length(), tmp);
238 QStringView pattern = qToStringViewIgnoringNull(tmp);
239#else
240 QStringView pattern = key;
241#endif
242 const auto valueStr = line.mid(equalPos + 1).trimmed();
243 int value = -1;
244 if (valueStr == QLatin1String("true"))
245 value = 1;
246 else if (valueStr == QLatin1String("false"))
247 value = 0;
248 QLoggingRule rule(pattern, (value == 1));
249 if (rule.flags != 0 && (value != -1))
250 _rules.append(std::move(rule));
251 else
252 warnMsg("Ignoring malformed logging rule: '%s'", line.toUtf8().constData());
253 } else {
254 warnMsg("Ignoring malformed logging rule: '%s'", line.toUtf8().constData());
255 }
256 }
257 }
258}
259
260/*!
261 \internal
262 QLoggingRegistry constructor
263 */
264QLoggingRegistry::QLoggingRegistry()
265 : categoryFilter(defaultCategoryFilter)
266{
267#if defined(Q_OS_ANDROID)
268 // Unless QCoreApplication has been constructed we can't be sure that
269 // we are on Qt's main thread. If we did allow logging here, we would
270 // potentially set Qt's main thread to Android's thread 0, which would
271 // confuse Qt later when running main().
272 if (!qApp)
273 return;
274#endif
275
276 initializeRules(); // Init on first use
277}
278
279static bool qtLoggingDebug()
280{
281 static const bool debugEnv = qEnvironmentVariableIsSet("QT_LOGGING_DEBUG");
282 return debugEnv;
283}
284
285static QList<QLoggingRule> loadRulesFromFile(const QString &filePath)
286{
287 QFile file(filePath);
288 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
289 if (qtLoggingDebug())
290 debugMsg("Loading \"%s\" ...",
291 QDir::toNativeSeparators(file.fileName()).toUtf8().constData());
292 QTextStream stream(&file);
293 QLoggingSettingsParser parser;
294 parser.setContent(stream);
295 return parser.rules();
296 }
297 return QList<QLoggingRule>();
298}
299
300/*!
301 \internal
302 Initializes the rules database by loading
303 $QT_LOGGING_CONF, $QT_LOGGING_RULES, and .config/QtProject/qtlogging.ini.
304 */
305void QLoggingRegistry::initializeRules()
306{
307 QList<QLoggingRule> er, qr, cr;
308 // get rules from environment
309 const QByteArray rulesFilePath = qgetenv("QT_LOGGING_CONF");
310 if (!rulesFilePath.isEmpty())
311 er = loadRulesFromFile(QFile::decodeName(rulesFilePath));
312
313 const QByteArray rulesSrc = qgetenv("QT_LOGGING_RULES").replace(';', '\n');
314 if (!rulesSrc.isEmpty()) {
315 QTextStream stream(rulesSrc);
316 QLoggingSettingsParser parser;
317 parser.setImplicitRulesSection(true);
318 parser.setContent(stream);
319 er += parser.rules();
320 }
321
322 const QString configFileName = QStringLiteral("qtlogging.ini");
323
324#if !defined(QT_BOOTSTRAPPED)
325 // get rules from Qt data configuration path
326 const QString qtConfigPath
327 = QDir(QLibraryInfo::path(QLibraryInfo::DataPath)).absoluteFilePath(configFileName);
328 qr = loadRulesFromFile(qtConfigPath);
329#endif
330
331 // get rules from user's/system configuration
332 const QString envPath = QStandardPaths::locate(QStandardPaths::GenericConfigLocation,
333 QString::fromLatin1("QtProject/") + configFileName);
334 if (!envPath.isEmpty())
335 cr = loadRulesFromFile(envPath);
336
337 const QMutexLocker locker(&registryMutex);
338
339 ruleSets[EnvironmentRules] = std::move(er);
340 ruleSets[QtConfigRules] = std::move(qr);
341 ruleSets[ConfigRules] = std::move(cr);
342
343 if (!ruleSets[EnvironmentRules].isEmpty() || !ruleSets[QtConfigRules].isEmpty() || !ruleSets[ConfigRules].isEmpty())
344 updateRules();
345}
346
347/*!
348 \internal
349 Registers a category object.
350
351 This method might be called concurrently for the same category object.
352*/
353void QLoggingRegistry::registerCategory(QLoggingCategory *cat, QtMsgType enableForLevel)
354{
355 const auto locker = qt_scoped_lock(registryMutex);
356
357 if (!categories.contains(cat)) {
358 categories.insert(cat, enableForLevel);
359 (*categoryFilter)(cat);
360 }
361}
362
363/*!
364 \internal
365 Unregisters a category object.
366*/
367void QLoggingRegistry::unregisterCategory(QLoggingCategory *cat)
368{
369 const auto locker = qt_scoped_lock(registryMutex);
370 categories.remove(cat);
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("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(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(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(categoryName, "qt") == 0 || strncmp(categoryName, "qt.", 3) == 0)
455 debug = false;
456 }
457
458 const auto categoryName = QLatin1String(cat->categoryName());
459
460 for (const auto &ruleSet : reg->ruleSets) {
461 for (const auto &rule : ruleSet) {
462 int filterpass = rule.pass(categoryName, QtDebugMsg);
463 if (filterpass != 0)
464 debug = (filterpass > 0);
465 filterpass = rule.pass(categoryName, QtInfoMsg);
466 if (filterpass != 0)
467 info = (filterpass > 0);
468 filterpass = rule.pass(categoryName, QtWarningMsg);
469 if (filterpass != 0)
470 warning = (filterpass > 0);
471 filterpass = rule.pass(categoryName, QtCriticalMsg);
472 if (filterpass != 0)
473 critical = (filterpass > 0);
474 }
475 }
476
477 cat->setEnabled(QtDebugMsg, debug);
478 cat->setEnabled(QtInfoMsg, info);
479 cat->setEnabled(QtWarningMsg, warning);
480 cat->setEnabled(QtCriticalMsg, critical);
481}
482
483
484QT_END_NAMESPACE
485