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