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 | |
61 | QT_BEGIN_NAMESPACE |
62 | |
63 | Q_GLOBAL_STATIC(QLoggingRegistry, qtLoggingRegistry) |
64 | |
65 | /*! |
66 | \internal |
67 | Constructs a logging rule with default values. |
68 | */ |
69 | QLoggingRule::QLoggingRule() : |
70 | enabled(false) |
71 | { |
72 | } |
73 | |
74 | /*! |
75 | \internal |
76 | Constructs a logging rule. |
77 | */ |
78 | QLoggingRule::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 | */ |
90 | int 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 | */ |
132 | void 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 | */ |
190 | void 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 | */ |
202 | void 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 | |
215 | void 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 | */ |
265 | QLoggingRegistry::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 | |
280 | static bool qtLoggingDebug() |
281 | { |
282 | static const bool debugEnv = qEnvironmentVariableIsSet(varName: "QT_LOGGING_DEBUG" ); |
283 | return debugEnv; |
284 | } |
285 | |
286 | static 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 | */ |
306 | void 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(®istryMutex); |
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 | */ |
354 | void 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 | */ |
368 | void 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 | */ |
378 | void 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(®istryMutex); |
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 | */ |
400 | void 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 | */ |
410 | QLoggingCategory::CategoryFilter |
411 | QLoggingRegistry::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 | |
426 | QLoggingRegistry *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 | */ |
437 | void 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 | |
485 | QT_END_NAMESPACE |
486 | |