123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 | // Copyright (C) 2022 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only#include"qloggingregistry_p.h"#include <QtCore/qfile.h>#include <QtCore/qlibraryinfo.h>#include <QtCore/private/qlocking_p.h>#include <QtCore/qstandardpaths.h>#include <QtCore/qstringtokenizer.h>#include <QtCore/qtextstream.h>#include <QtCore/qdir.h>#include <QtCore/qcoreapplication.h>#include <QtCore/qscopedvaluerollback.h>#if QT_CONFIG(settings)#include <QtCore/qsettings.h>#include <QtCore/private/qsettings_p.h>#endif// We can't use the default macros because this would lead to recursion.// Instead let's define our own one that unconditionally logs...#define debugMsg QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC,"qt.core.logging").debug#define warnMsg QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC,"qt.core.logging").warning QT_BEGIN_NAMESPACE using namespaceQt::StringLiterals;Q_GLOBAL_STATIC(QLoggingRegistry, qtLoggingRegistry)alignas(QLoggingCategory)static unsigned char defaultLoggingCategory[sizeof(QLoggingCategory)];/*! \internal Constructs a logging rule with default values.*/QLoggingRule::QLoggingRule(){}/*! \internal Constructs a logging rule.*/QLoggingRule::QLoggingRule(QStringView pattern,bool enabled) :enabled(enabled){parse(pattern);}/*! \internal Return value 1 means filter passed, 0 means filter doesn't influence this category, -1 means category doesn't pass this filter. */intQLoggingRule::pass(QLatin1StringView cat, QtMsgType msgType)const{// check message typeif(messageType > -1&& messageType != msgType)return0;if(flags == FullText) {// full matchif(category == cat)return(enabled ?1: -1);elsereturn0;}const qsizetype idx = cat.indexOf(category);if(idx >=0) {if(flags == MidFilter) {// matches somewherereturn(enabled ?1: -1);}else if(flags == LeftFilter) {// matches leftif(idx ==0)return(enabled ?1: -1);}else if(flags == RightFilter) {// matches rightif(idx == (cat.size() - category.size()))return(enabled ?1: -1);}}return0;}/*! \internal Parses \a pattern. Allowed is f.ex.: qt.core.io.debug FullText, QtDebugMsg qt.core.* LeftFilter, all types *.io.warning RightFilter, QtWarningMsg *.core.* MidFilter */voidQLoggingRule::parse(QStringView pattern){ QStringView p;// strip trailing ".messagetype"if(pattern.endsWith(".debug"_L1)) { p = pattern.chopped(6);// strlen(".debug") messageType = QtDebugMsg;}else if(pattern.endsWith(".info"_L1)) { p = pattern.chopped(5);// strlen(".info") messageType = QtInfoMsg;}else if(pattern.endsWith(".warning"_L1)) { p = pattern.chopped(8);// strlen(".warning") messageType = QtWarningMsg;}else if(pattern.endsWith(".critical"_L1)) { p = pattern.chopped(9);// strlen(".critical") messageType = QtCriticalMsg;}else{ p = pattern;}const QChar asterisk = u'*';if(!p.contains(asterisk)) { flags = FullText;}else{if(p.endsWith(asterisk)) { flags |= LeftFilter; p = p.chopped(1);}if(p.startsWith(asterisk)) { flags |= RightFilter; p = p.mid(1);}if(p.contains(asterisk))// '*' only supported at start/end flags =PatternFlags();} category = p.toString();}/*! \class QLoggingSettingsParser \since 5.3 \internal Parses a .ini file with the following format: [rules] rule1=[true|false] rule2=[true|false] ... [rules] is the default section, and therefore optional.*//*! \internal Parses configuration from \a content.*/voidQLoggingSettingsParser::setContent(QStringView content){ _rules.clear();for(auto line :qTokenize(content, u'\n'))parseNextLine(line);}/*! \internal Parses configuration from \a stream.*/voidQLoggingSettingsParser::setContent(QTextStream &stream){ _rules.clear(); QString line;while(stream.readLineInto(&line))parseNextLine(qToStringViewIgnoringNull(line));}/*! \internal Parses one line of the configuration file*/voidQLoggingSettingsParser::parseNextLine(QStringView line){// Remove whitespace at start and end of line: line = line.trimmed();// commentif(line.startsWith(u';'))return;if(line.startsWith(u'[') && line.endsWith(u']')) {// new sectionauto sectionName = line.mid(1).chopped(1).trimmed(); m_inRulesSection = sectionName.compare("rules"_L1,Qt::CaseInsensitive) ==0;return;}if(m_inRulesSection) {const qsizetype equalPos = line.indexOf(u'=');if(equalPos != -1) {if(line.lastIndexOf(u'=') == equalPos) {constauto key = line.left(equalPos).trimmed();#if QT_CONFIG(settings) QString tmp;QSettingsPrivate::iniUnescapedKey(key.toUtf8(), tmp); QStringView pattern =qToStringViewIgnoringNull(tmp);#else QStringView pattern = key;#endifconstauto valueStr = line.mid(equalPos +1).trimmed();int value = -1;if(valueStr =="true"_L1) value =1;else if(valueStr =="false"_L1) value =0; QLoggingRule rule(pattern, (value ==1));if(rule.flags !=0&& (value != -1)) _rules.append(std::move(rule));elsewarnMsg("Ignoring malformed logging rule: '%s'", line.toUtf8().constData());}else{warnMsg("Ignoring malformed logging rule: '%s'", line.toUtf8().constData());}}}}/*! \internal QLoggingRegistry constructor */QLoggingRegistry::QLoggingRegistry():categoryFilter(defaultCategoryFilter){using U =QLoggingCategory::UnregisteredInitialization;Q_ASSERT_X(!self,"QLoggingRegistry","Singleton recreated"); self =this;// can't use std::construct_at here - private constructorauto cat =new(defaultLoggingCategory)QLoggingCategory(U{}, defaultCategoryName); categories.emplace(cat, QtDebugMsg);#if defined(Q_OS_ANDROID)// Unless QCoreApplication has been constructed we can't be sure that// we are on Qt's main thread. If we did allow logging here, we would// potentially set Qt's main thread to Android's thread 0, which would// confuse Qt later when running main().if(!qApp)return;#endifinitializeRules();// Init on first use}static boolqtLoggingDebug(){static const bool debugEnv = [] {bool debug =qEnvironmentVariableIsSet("QT_LOGGING_DEBUG");if(debug)debugMsg("QT_LOGGING_DEBUG environment variable is set.");return debug;}();returnQ_UNLIKELY(debugEnv);}static QList<QLoggingRule>loadRulesFromFile(const QString &filePath){if(qtLoggingDebug()) {debugMsg("Checking\"%s\"for rules",QDir::toNativeSeparators(filePath).toUtf8().constData());} QFile file(filePath);if(file.open(QIODevice::ReadOnly |QIODevice::Text)) { QTextStream stream(&file); QLoggingSettingsParser parser; parser.setContent(stream);if(qtLoggingDebug())debugMsg("Loaded %td rules",static_cast<ptrdiff_t>(parser.rules().size()));return parser.rules();}return QList<QLoggingRule>();}/*! \internal Initializes the rules database by loading $QT_LOGGING_CONF, $QT_LOGGING_RULES, and .config/QtProject/qtlogging.ini. */voidQLoggingRegistry::initializeRules(){if(qtLoggingDebug()) {debugMsg("Initializing the rules database ...");debugMsg("Checking %s environment variable","QT_LOGGING_CONF");} QList<QLoggingRule> er, qr, cr;// get rules from environmentif(QString rulesFilePath =qEnvironmentVariable("QT_LOGGING_CONF"); !rulesFilePath.isEmpty()) er =loadRulesFromFile(rulesFilePath);if(qtLoggingDebug())debugMsg("Checking %s environment variable","QT_LOGGING_RULES");const QByteArray rulesSrc =qgetenv("QT_LOGGING_RULES").replace(';','\n');if(!rulesSrc.isEmpty()) { QTextStream stream(rulesSrc); QLoggingSettingsParser parser; parser.setImplicitRulesSection(true); parser.setContent(stream);if(qtLoggingDebug())debugMsg("Loaded %td rules",static_cast<ptrdiff_t>(parser.rules().size())); er += parser.rules();}const QString configFileName = u"QtProject/qtlogging.ini"_s; QStringView baseConfigFileName =QStringView(configFileName).sliced(strlen("QtProject"));Q_ASSERT(baseConfigFileName.startsWith(u'/'));// get rules from Qt data configuration path qr =loadRulesFromFile(QLibraryInfo::path(QLibraryInfo::DataPath) + baseConfigFileName);// get rules from user's/system configuration// locateAll() returns the user's file (most overriding) firstconst QStringList configPaths =QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, configFileName);for(qsizetype i = configPaths.size(); i >0; --i) cr +=loadRulesFromFile(configPaths[i -1]);const QMutexLocker locker(®istryMutex); ruleSets[EnvironmentRules] =std::move(er); ruleSets[QtConfigRules] =std::move(qr); ruleSets[ConfigRules] =std::move(cr);if(!ruleSets[EnvironmentRules].isEmpty() || !ruleSets[QtConfigRules].isEmpty() || !ruleSets[ConfigRules].isEmpty())updateRules();}/*! \internal Registers a category object. This method might be called concurrently for the same category object.*/voidQLoggingRegistry::registerCategory(QLoggingCategory *cat, QtMsgType enableForLevel){constauto locker =qt_scoped_lock(registryMutex);auto r = categories.tryEmplace(cat, enableForLevel);if(r.inserted) {// new entry(*categoryFilter)(cat);}}/*! \internal Unregisters a category object.*/voidQLoggingRegistry::unregisterCategory(QLoggingCategory *cat){constauto locker =qt_scoped_lock(registryMutex); categories.remove(cat);}/*! \since 6.3 \internal Registers the environment variable \a environment as the control variable for enabling debugging by default for category \a categoryName. The category name must start with "qt."*/voidQLoggingRegistry::registerEnvironmentOverrideForCategory(const char*categoryName,const char*environment){ qtCategoryEnvironmentOverrides.insert_or_assign(categoryName, environment);}/*! \internal Installs logging rules as specified in \a content. */voidQLoggingRegistry::setApiRules(const QString &content){ QLoggingSettingsParser parser; parser.setImplicitRulesSection(true); parser.setContent(content);if(qtLoggingDebug())debugMsg("Loading logging rules set by QLoggingCategory::setFilterRules ...");const QMutexLocker locker(®istryMutex); ruleSets[ApiRules] = parser.rules();updateRules();}/*! \internal Activates a new set of logging rules for the default filter. (The caller must lock registryMutex to make sure the API is thread safe.)*/voidQLoggingRegistry::updateRules(){for(auto it = categories.keyBegin(), end = categories.keyEnd(); it != end; ++it)(*categoryFilter)(*it);}/*! \internal Installs a custom filter rule.*/QLoggingCategory::CategoryFilter QLoggingRegistry::installFilter(QLoggingCategory::CategoryFilter filter){constauto locker =qt_scoped_lock(registryMutex);if(!filter) filter = defaultCategoryFilter;QLoggingCategory::CategoryFilter old = categoryFilter; categoryFilter = filter;updateRules();return old;} QLoggingRegistry *QLoggingRegistry::instance(){ Q_CONSTINIT thread_local bool recursionGuard =false;if(recursionGuard)returnnullptr; QScopedValueRollback<bool>rollback(recursionGuard,true);returnqtLoggingRegistry();} QLoggingCategory *QLoggingRegistry::defaultCategory(){// Initialize the defaultLoggingCategory global static, if necessary. Note// how it remains initialized forever, even if the QLoggingRegistry// instance() is destroyed.instance();// std::launder() to be on the safe side, but it's unnecessary because the// object is never recreated.returnstd::launder(reinterpret_cast<QLoggingCategory *>(defaultLoggingCategory));}/*! \internal Updates category settings according to rules. As a category filter, it is run with registryMutex held.*/voidQLoggingRegistry::defaultCategoryFilter(QLoggingCategory *cat){const QLoggingRegistry *reg = self;Q_ASSERT(reg->categories.contains(cat)); QtMsgType enableForLevel = reg->categories.value(cat);// NB: note that the numeric values of the Qt*Msg constants are// not in severity order.bool debug = (enableForLevel == QtDebugMsg);bool info = debug || (enableForLevel == QtInfoMsg);bool warning = info || (enableForLevel == QtWarningMsg);bool critical = warning || (enableForLevel == QtCriticalMsg);// hard-wired implementation of// qt.*.debug=false// qt.debug=falseif(const char*categoryName = cat->categoryName()) {// == "qt" or startsWith("qt.")if(strcmp(categoryName,"qt") ==0) { debug =false;}else if(strncmp(categoryName,"qt.",3) ==0) {// may be overriddenauto it = reg->qtCategoryEnvironmentOverrides.find(categoryName);if(it == reg->qtCategoryEnvironmentOverrides.end()) debug =false;else debug =qEnvironmentVariableIntValue(it->second);}}constauto categoryName =QLatin1StringView(cat->categoryName());for(constauto&ruleSet : reg->ruleSets) {for(constauto&rule : ruleSet) {int filterpass = rule.pass(categoryName, QtDebugMsg);if(filterpass !=0) debug = (filterpass >0); filterpass = rule.pass(categoryName, QtInfoMsg);if(filterpass !=0) info = (filterpass >0); filterpass = rule.pass(categoryName, QtWarningMsg);if(filterpass !=0) warning = (filterpass >0); filterpass = rule.pass(categoryName, QtCriticalMsg);if(filterpass !=0) critical = (filterpass >0);}} cat->setEnabled(QtDebugMsg, debug); cat->setEnabled(QtInfoMsg, info); cat->setEnabled(QtWarningMsg, warning); cat->setEnabled(QtCriticalMsg, critical);} QT_END_NAMESPACE
|