qtbase/src/corelib/io/qloggingregistry.cpp
Marc Mutz a3303aceeb QLoggingRegistry: avoid double-lookup
The code used the if (!contains()) { insert() } anti-pattern,
necessitated by Qt's deviation from the STL of allowing insert() to
overwrite an existing entry, causing two lookups of the same key.

Fix by recording the size prior to the execution of the indexing
operator and taking a size increase as the cue to populate the (new)
entry. This way, we look up the key only once.

Change-Id: Ica039035fe9ea4b88c20184784c324c9fac33d49
Reviewed-by: Kai Koehne <kai.koehne@qt.io>
2021-11-19 16:28:45 +01:00

509 lines
15 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#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>
#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
Q_GLOBAL_STATIC(QLoggingRegistry, qtLoggingRegistry)
/*!
\internal
Constructs a logging rule with default values.
*/
QLoggingRule::QLoggingRule() :
enabled(false)
{
}
/*!
\internal
Constructs a logging rule.
*/
QLoggingRule::QLoggingRule(QStringView pattern, bool enabled) :
messageType(-1),
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.
*/
int QLoggingRule::pass(QLatin1String cat, QtMsgType msgType) const
{
// check message type
if (messageType > -1 && messageType != msgType)
return 0;
if (flags == FullText) {
// full match
if (category == cat)
return (enabled ? 1 : -1);
else
return 0;
}
const int idx = cat.indexOf(category);
if (idx >= 0) {
if (flags == MidFilter) {
// matches somewhere
return (enabled ? 1 : -1);
} else if (flags == LeftFilter) {
// matches left
if (idx == 0)
return (enabled ? 1 : -1);
} else if (flags == RightFilter) {
// matches right
if (idx == (cat.size() - category.count()))
return (enabled ? 1 : -1);
}
}
return 0;
}
/*!
\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
*/
void QLoggingRule::parse(QStringView pattern)
{
QStringView p;
// strip trailing ".messagetype"
if (pattern.endsWith(QLatin1String(".debug"))) {
p = pattern.chopped(6); // strlen(".debug")
messageType = QtDebugMsg;
} else if (pattern.endsWith(QLatin1String(".info"))) {
p = pattern.chopped(5); // strlen(".info")
messageType = QtInfoMsg;
} else if (pattern.endsWith(QLatin1String(".warning"))) {
p = pattern.chopped(8); // strlen(".warning")
messageType = QtWarningMsg;
} else if (pattern.endsWith(QLatin1String(".critical"))) {
p = pattern.chopped(9); // strlen(".critical")
messageType = QtCriticalMsg;
} else {
p = pattern;
}
if (!p.contains(QLatin1Char('*'))) {
flags = FullText;
} else {
if (p.endsWith(QLatin1Char('*'))) {
flags |= LeftFilter;
p = p.chopped(1);
}
if (p.startsWith(QLatin1Char('*'))) {
flags |= RightFilter;
p = p.mid(1);
}
if (p.contains(QLatin1Char('*'))) // '*' 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.
*/
void QLoggingSettingsParser::setContent(QStringView content)
{
_rules.clear();
for (auto line : qTokenize(content, u'\n'))
parseNextLine(line);
}
/*!
\internal
Parses configuration from \a stream.
*/
void QLoggingSettingsParser::setContent(QTextStream &stream)
{
_rules.clear();
QString line;
while (stream.readLineInto(&line))
parseNextLine(qToStringViewIgnoringNull(line));
}
/*!
\internal
Parses one line of the configuration file
*/
void QLoggingSettingsParser::parseNextLine(QStringView line)
{
// Remove whitespace at start and end of line:
line = line.trimmed();
// comment
if (line.startsWith(QLatin1Char(';')))
return;
if (line.startsWith(QLatin1Char('[')) && line.endsWith(QLatin1Char(']'))) {
// new section
auto sectionName = line.mid(1).chopped(1).trimmed();
m_inRulesSection = sectionName.compare(QLatin1String("rules"), Qt::CaseInsensitive) == 0;
return;
}
if (m_inRulesSection) {
int equalPos = line.indexOf(QLatin1Char('='));
if (equalPos != -1) {
if (line.lastIndexOf(QLatin1Char('=')) == equalPos) {
const auto key = line.left(equalPos).trimmed();
#if QT_CONFIG(settings)
QString tmp;
QSettingsPrivate::iniUnescapedKey(key.toUtf8(), 0, key.length(), tmp);
QStringView pattern = qToStringViewIgnoringNull(tmp);
#else
QStringView pattern = key;
#endif
const auto valueStr = line.mid(equalPos + 1).trimmed();
int value = -1;
if (valueStr == QLatin1String("true"))
value = 1;
else if (valueStr == QLatin1String("false"))
value = 0;
QLoggingRule rule(pattern, (value == 1));
if (rule.flags != 0 && (value != -1))
_rules.append(std::move(rule));
else
warnMsg("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)
{
#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;
#endif
initializeRules(); // Init on first use
}
static bool qtLoggingDebug()
{
static const bool debugEnv = qEnvironmentVariableIsSet("QT_LOGGING_DEBUG");
return debugEnv;
}
static QList<QLoggingRule> loadRulesFromFile(const QString &filePath)
{
QFile file(filePath);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
if (qtLoggingDebug())
debugMsg("Loading \"%s\" ...",
QDir::toNativeSeparators(file.fileName()).toUtf8().constData());
QTextStream stream(&file);
QLoggingSettingsParser parser;
parser.setContent(stream);
return parser.rules();
}
return QList<QLoggingRule>();
}
/*!
\internal
Initializes the rules database by loading
$QT_LOGGING_CONF, $QT_LOGGING_RULES, and .config/QtProject/qtlogging.ini.
*/
void QLoggingRegistry::initializeRules()
{
QList<QLoggingRule> er, qr, cr;
// get rules from environment
const QByteArray rulesFilePath = qgetenv("QT_LOGGING_CONF");
if (!rulesFilePath.isEmpty())
er = loadRulesFromFile(QFile::decodeName(rulesFilePath));
const QByteArray rulesSrc = qgetenv("QT_LOGGING_RULES").replace(';', '\n');
if (!rulesSrc.isEmpty()) {
QTextStream stream(rulesSrc);
QLoggingSettingsParser parser;
parser.setImplicitRulesSection(true);
parser.setContent(stream);
er += parser.rules();
}
const QString configFileName = QStringLiteral("qtlogging.ini");
#if !defined(QT_BOOTSTRAPPED)
// get rules from Qt data configuration path
const QString qtConfigPath
= QDir(QLibraryInfo::path(QLibraryInfo::DataPath)).absoluteFilePath(configFileName);
qr = loadRulesFromFile(qtConfigPath);
#endif
// get rules from user's/system configuration
const QString envPath = QStandardPaths::locate(QStandardPaths::GenericConfigLocation,
QString::fromLatin1("QtProject/") + configFileName);
if (!envPath.isEmpty())
cr = loadRulesFromFile(envPath);
const QMutexLocker locker(&registryMutex);
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.
*/
void QLoggingRegistry::registerCategory(QLoggingCategory *cat, QtMsgType enableForLevel)
{
const auto locker = qt_scoped_lock(registryMutex);
const auto oldSize = categories.size();
auto &e = categories[cat];
if (categories.size() != oldSize) {
// new entry
e = enableForLevel;
(*categoryFilter)(cat);
}
}
/*!
\internal
Unregisters a category object.
*/
void QLoggingRegistry::unregisterCategory(QLoggingCategory *cat)
{
const auto 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."
*/
void QLoggingRegistry::registerEnvironmentOverrideForCategory(QByteArrayView categoryName,
QByteArrayView environment)
{
qtCategoryEnvironmentOverrides.insert(categoryName, environment);
}
/*!
\internal
Installs logging rules as specified in \a content.
*/
void QLoggingRegistry::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(&registryMutex);
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.)
*/
void QLoggingRegistry::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)
{
const auto locker = qt_scoped_lock(registryMutex);
if (!filter)
filter = defaultCategoryFilter;
QLoggingCategory::CategoryFilter old = categoryFilter;
categoryFilter = filter;
updateRules();
return old;
}
QLoggingRegistry *QLoggingRegistry::instance()
{
return qtLoggingRegistry();
}
/*!
\internal
Updates category settings according to rules.
As a category filter, it is run with registryMutex held.
*/
void QLoggingRegistry::defaultCategoryFilter(QLoggingCategory *cat)
{
const QLoggingRegistry *reg = QLoggingRegistry::instance();
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=false
if (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 overridden
auto it = reg->qtCategoryEnvironmentOverrides.find(categoryName);
if (it == reg->qtCategoryEnvironmentOverrides.end())
debug = false;
else
debug = qEnvironmentVariableIntValue(it.value().data());
}
}
const auto categoryName = QLatin1String(cat->categoryName());
for (const auto &ruleSet : reg->ruleSets) {
for (const auto &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