Allow configuration of logging rules from file system

Allow configuration of logging rules from outside of the application,
either through a configuration file (.config/QtProject/qtlogging.ini),
or through a file specified by a QT_LOGGING_CONF environment
variable.

The logging rules from the different sources are concatenated: First
the rules from QtProject/qtlogging.ini are applied, then
QLoggingCategory::setLoggingRules(), finally from the environment.
This allows an application to overwrite/augment the system wide rules,
and in turn that can be tailored for a specific run by setting a
configuration in the environment variable.

[ChangeLog][QtCore][Logging] The logging framework can now be configured
with an .ini file.

Change-Id: I442efde1b7e0a2ebe135c6f6e0a4b656483fe4b1
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Kai Koehne 2014-02-04 16:35:26 +01:00 committed by The Qt Project
parent dc09a02e3a
commit a2bfd11493
8 changed files with 378 additions and 58 deletions

View File

@ -86,12 +86,62 @@ Q_GLOBAL_STATIC_WITH_ARGS(QLoggingCategory, qtDefaultCategory,
In the default configuration \l isWarningEnabled() , \l isDebugEnabled() and
\l isCriticalEnabled() will return \c true.
\section1 Changing the configuration of a category
\section1 Configuring Categories
Use either \l setFilterRules() or \l installFilter() to
configure categories, for example
Categories can be centrally configured by either setting logging rules,
or by installing a custom filter.
\snippet qloggingcategory/main.cpp 2
\section2 Logging Rules
Logging rules allow to enable or disable logging for categories in a flexible
way. Rules are specified in text, where every line must have the format
\code
<category>[.<type>] = true|false
\endcode
\c <category> is the name of the category, potentially with \c{*} as a
wildcard symbol as the first or last character (or at both positions).
The optional \c <type> must be either \c debug, \c warning, or \c critical.
Lines that do not fit to his scheme are ignored.
Rules are evaluated in text order, from first to last. That is, if two rules
apply to a category/type, the rule that comes later is applied.
Rules can be set via \l setFilterRules(). Since Qt 5.3 logging rules
are also automatically loaded from the \c [rules] section of a logging
configuration file. Such configuration files are looked up in the QtProject
configuration directory, or explicitly set in a \c QT_LOGGING_CONF
environment variable.
Rules set by \l setFilterRules() take precedence over rules specified
in the QtProject configuration directory, and can, in turn, be
overwritten by rules from the configuration file specified by
\c QT_LOGGING_CONF.
Order of evaluation:
\list
\li Rules from QtProject/qlogging.ini
\li Rules set by \l setFilterRules()
\li Rules from file in \c QT_LOGGING_CONF
\endlist
The \c QtProject/qlogging.ini file is looked up in all directories returned
by QStandardPaths::GenericConfigLocation, e.g.
\list
\li on Mac OS X: \c ~/Library/Preferences
\li on Unix: \c ~/.config, \c /etc/xdg
\li on Windows: \c %LOCALAPPDATA%, \c %ProgramData%,
\l QCoreApplication::applicationDirPath(),
QCoreApplication::applicationDirPath() + \c "/data"
\endlist
\section2 Installing a Custom Filter
As a lower-level alternative to the text rules you can also implement a
custom filter via \l installFilter(). All filter rules are ignored in this
case.
\section1 Printing the category
@ -278,27 +328,18 @@ QLoggingCategory::installFilter(QLoggingCategory::CategoryFilter filter)
Configures which categories and message types should be enabled through a
a set of \a rules.
Each line in \a rules must have the format
\code
<category>[.<type>] = true|false
\endcode
where \c <category> is the name of the category, potentially with \c{*} as a
wildcard symbol at the start and/or the end. The optional \c <type> must
be either \c debug, \c warning, or \c critical.
Example:
\snippet qloggingcategory/main.cpp 2
\note The rules might be ignored if a custom category filter is installed
with \l installFilter().
with \l installFilter(), or if the user defined a custom logging
configuration file in the \c QT_LOGGING_CONF environment variable.
*/
void QLoggingCategory::setFilterRules(const QString &rules)
{
QLoggingRegistry::instance()->rulesParser.setRules(rules);
QLoggingRegistry::instance()->setApiRules(rules);
}
/*!

View File

@ -42,6 +42,10 @@
#include "qloggingregistry_p.h"
#include "qloggingcategory_p.h"
#include <QtCore/qfile.h>
#include <QtCore/qstandardpaths.h>
#include <QtCore/qtextstream.h>
QT_BEGIN_NAMESPACE
Q_GLOBAL_STATIC(QLoggingRegistry, qtLoggingRegistry)
@ -150,33 +154,38 @@ void QLoggingRule::parse()
}
/*!
\class QLoggingSettingsParser
\since 5.3
\internal
Creates a new QLoggingRules object.
Parses a .ini file with the following format:
[rules]
rule1=[true|false]
rule2=[true|false]
...
[rules] is the default section, and therefore optional.
*/
QLoggingRulesParser::QLoggingRulesParser(QLoggingRegistry *registry) :
registry(registry)
{
}
/*!
\internal
Sets logging rules string.
Parses configuration from \a content.
*/
void QLoggingRulesParser::setRules(const QString &content)
void QLoggingSettingsParser::setContent(const QString &content)
{
QString content_ = content;
QTextStream stream(&content_, QIODevice::ReadOnly);
parseRules(stream);
setContent(stream);
}
/*!
\internal
Parses rules out of a QTextStream.
Parses configuration from \a stream.
*/
void QLoggingRulesParser::parseRules(QTextStream &stream)
void QLoggingSettingsParser::setContent(QTextStream &stream)
{
QVector<QLoggingRule> rules;
_rules.clear();
while (!stream.atEnd()) {
QString line = stream.readLine();
@ -184,29 +193,77 @@ void QLoggingRulesParser::parseRules(QTextStream &stream)
line = line.simplified();
line.remove(QLatin1Char(' '));
const QStringList pair = line.split(QLatin1Char('='));
if (pair.count() == 2) {
const QString pattern = pair.at(0);
bool enabled = (QString::compare(pair.at(1),
QLatin1String("true"),
Qt::CaseInsensitive) == 0);
rules.append(QLoggingRule(pattern, enabled));
// comment
if (line.startsWith(QLatin1Char(';')))
continue;
if (line.startsWith(QLatin1Char('[')) && line.endsWith(QLatin1Char(']'))) {
// new section
_section = line.mid(1, line.size() - 2);
continue;
}
if (_section == QLatin1String("rules")) {
int equalPos = line.indexOf(QLatin1Char('='));
if ((equalPos != -1)
&& (line.lastIndexOf(QLatin1Char('=')) == equalPos)) {
const QString pattern = line.left(equalPos);
const QStringRef value = line.midRef(equalPos + 1);
bool enabled = (value.compare(QLatin1String("true"),
Qt::CaseInsensitive) == 0);
_rules.append(QLoggingRule(pattern, enabled));
}
}
}
registry->setRules(rules);
}
/*!
\internal
QLoggingPrivate constructor
QLoggingRegistry constructor
*/
QLoggingRegistry::QLoggingRegistry()
: rulesParser(this),
categoryFilter(defaultCategoryFilter)
: categoryFilter(defaultCategoryFilter)
{
}
/*!
\internal
Initializes the rules database by loading
.config/QtProject/qtlogging.ini and $QT_LOGGING_CONF.
*/
void QLoggingRegistry::init()
{
// get rules from environment
const QByteArray rulesFilePath = qgetenv("QT_LOGGING_CONF");
if (!rulesFilePath.isEmpty()) {
QFile file(QFile::decodeName(rulesFilePath));
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream stream(&file);
QLoggingSettingsParser parser;
parser.setContent(stream);
envRules = parser.rules();
}
}
// get rules from qt configuration
QString envPath = QStandardPaths::locate(QStandardPaths::GenericConfigLocation,
QStringLiteral("QtProject/qtlogging.ini"));
if (!envPath.isEmpty()) {
QFile file(envPath);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream stream(&file);
QLoggingSettingsParser parser;
parser.setContent(stream);
configRules = parser.rules();
}
}
if (!envRules.isEmpty() || !configRules.isEmpty()) {
QMutexLocker locker(&registryMutex);
updateRules();
}
}
/*!
\internal
Registers a category object.
@ -236,17 +293,33 @@ void QLoggingRegistry::unregisterCategory(QLoggingCategory *cat)
/*!
\internal
Activates a new set of logging rules for the default filter.
*/
void QLoggingRegistry::setRules(const QVector<QLoggingRule> &rules_)
Installs logging rules as specified in \a content.
*/
void QLoggingRegistry::setApiRules(const QString &content)
{
QLoggingSettingsParser parser;
parser.setSection(QStringLiteral("rules"));
parser.setContent(content);
QMutexLocker locker(&registryMutex);
apiRules = parser.rules();
rules = 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()
{
if (categoryFilter != defaultCategoryFilter)
return;
rules = configRules + apiRules + envRules;
foreach (QLoggingCategory *cat, categories)
(*categoryFilter)(cat);
}

View File

@ -60,6 +60,8 @@
#include <QtCore/qtextstream.h>
#include <QtCore/qvector.h>
class tst_QLoggingRegistry;
QT_BEGIN_NAMESPACE
class QLoggingRule
@ -89,45 +91,53 @@ private:
Q_DECLARE_OPERATORS_FOR_FLAGS(QLoggingRule::PatternFlags)
Q_DECLARE_TYPEINFO(QLoggingRule, Q_MOVABLE_TYPE);
class QLoggingRulesParser
class Q_AUTOTEST_EXPORT QLoggingSettingsParser
{
private:
explicit QLoggingRulesParser(class QLoggingRegistry *logging);
public:
void setRules(const QString &content);
void setSection(const QString &section) { _section = section; }
void setContent(const QString &content);
void setContent(QTextStream &stream);
QVector<QLoggingRule> rules() const { return _rules; }
private:
void parseRules(QTextStream &stream);
QLoggingRegistry *registry;
friend class QLoggingRegistry;
QString _section;
QVector<QLoggingRule> _rules;
};
class QLoggingRegistry
class Q_AUTOTEST_EXPORT QLoggingRegistry
{
public:
QLoggingRegistry();
void init();
void registerCategory(QLoggingCategory *category);
void unregisterCategory(QLoggingCategory *category);
void setRules(const QVector<QLoggingRule> &rules);
void setApiRules(const QString &content);
QLoggingCategory::CategoryFilter
installFilter(QLoggingCategory::CategoryFilter filter);
static QLoggingRegistry *instance();
QLoggingRulesParser rulesParser;
private:
void updateRules();
static void defaultCategoryFilter(QLoggingCategory *category);
QMutex registryMutex;
QVector<QLoggingRule> configRules;
QVector<QLoggingRule> envRules;
QVector<QLoggingRule> apiRules;
QVector<QLoggingRule> rules;
QList<QLoggingCategory*> categories;
QLoggingCategory::CategoryFilter categoryFilter;
friend class ::tst_QLoggingRegistry;
};
QT_END_NAMESPACE

View File

@ -55,6 +55,7 @@
#include <qfileinfo.h>
#include <qhash.h>
#include <qmutex.h>
#include <private/qloggingregistry_p.h>
#include <private/qprocess_p.h>
#include <qstandardpaths.h>
#include <qtextcodec.h>
@ -722,6 +723,10 @@ void QCoreApplication::init()
Q_ASSERT_X(!self, "QCoreApplication", "there should be only one application object");
QCoreApplication::self = this;
#ifndef QT_BOOTSTRAPPED
QLoggingRegistry::instance()->init();
#endif
#ifndef QT_NO_QOBJECT
// use the event dispatcher created by the app programmer (if any)
if (!QCoreApplicationPrivate::eventDispatcher)

View File

@ -17,6 +17,7 @@ SUBDIRS=\
qipaddress \
qlockfile \
qloggingcategory \
qloggingregistry \
qnodebug \
qprocess \
qprocess-noapplication \
@ -51,7 +52,8 @@ SUBDIRS=\
qabstractfileengine \
qfileinfo \
qipaddress \
qurlinternal
qurlinternal \
qloggingregistry
win32:!contains(QT_CONFIG, private_tests): SUBDIRS -= \
qfilesystementry

View File

@ -0,0 +1,8 @@
TEMPLATE = app
TARGET = tst_qloggingregistry
CONFIG += testcase
QT = core core-private testlib
SOURCES += tst_qloggingregistry.cpp
OTHER_FILES += qtlogging.ini

View File

@ -0,0 +1,2 @@
[rules]
*=true

View File

@ -0,0 +1,179 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite 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 Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest>
#include <QLoggingCategory>
#include <QtCore/private/qloggingregistry_p.h>
QT_USE_NAMESPACE
class tst_QLoggingRegistry : public QObject
{
Q_OBJECT
private slots:
void initTestCase()
{
// ensure a clean environment
QStandardPaths::setTestModeEnabled(true);
qunsetenv("QT_LOGGING_CONF");
}
void QLoggingSettingsParser_iniStyle()
{
//
// Logging configuration can be described
// in an .ini file. [rules] is the
// default category, and optional ...
//
QLoggingSettingsParser parser;
parser.setContent("[rules]\n"
"default=false\n"
"default=true");
QCOMPARE(parser.rules().size(), 2);
parser.setContent("[rules]\n"
"default=false");
QCOMPARE(parser.rules().size(), 1);
parser.setContent("[OtherSection]\n"
"default=false");
QCOMPARE(parser.rules().size(), 0);
}
void QLoggingRegistry_environment()
{
//
// Check whether QT_LOGGING_CONF is picked up from environment
//
qputenv("QT_LOGGING_CONF", QFINDTESTDATA("qtlogging.ini").toLocal8Bit());
QLoggingRegistry registry;
registry.init();
QCOMPARE(registry.apiRules.size(), 0);
QCOMPARE(registry.configRules.size(), 0);
QCOMPARE(registry.envRules.size(), 1);
QCOMPARE(registry.rules.size(), 1);
}
void QLoggingRegistry_config()
{
//
// Check whether QtProject/qtlogging.ini is loaded automatically
//
// first try to create a test file..
QString path = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QVERIFY(!path.isEmpty());
QDir dir(path + "/QtProject");
if (!dir.exists())
QVERIFY(dir.mkpath(path + "/QtProject"));
QFile file(dir.absoluteFilePath("qtlogging.ini"));
QVERIFY(file.open(QFile::WriteOnly | QFile::Text));
QTextStream out(&file);
out << "[rules]\n";
out << "Digia.*=false\n";
file.close();
QLoggingRegistry registry;
registry.init();
QCOMPARE(registry.configRules.size(), 1);
// remove file again
QVERIFY(file.remove());
}
void QLoggingRegistry_rulePriorities()
{
//
// Rules can stem from 3 sources:
// via QLoggingCategory::setFilterRules (API)
// via qtlogging.ini file in settings (Config)
// via QT_LOGGING_CONF environment variable (Env)
//
// Rules set by environment should get higher precedence than qtlogging.conf,
// than QLoggingCategory::setFilterRules
//
QLoggingCategory cat("Digia.Berlin");
QLoggingRegistry *registry = QLoggingRegistry::instance();
// empty all rules , check default
registry->rules.clear();
registry->apiRules.clear();
registry->configRules.clear();
registry->envRules.clear();
registry->updateRules();
QVERIFY(cat.isWarningEnabled());
// set Config rule
QLoggingSettingsParser parser;
parser.setContent("[rules]\nDigia.*=false");
registry->configRules=parser.rules();
registry->updateRules();
QVERIFY(!cat.isWarningEnabled());
// set API rule, should overwrite API one
QLoggingCategory::setFilterRules("Digia.*=true");
QVERIFY(cat.isWarningEnabled());
// set Env rule, should overwrite Config one
parser.setContent("Digia.*=false");
registry->envRules=parser.rules();
registry->updateRules();
QVERIFY(!cat.isWarningEnabled());
}
};
QTEST_MAIN(tst_QLoggingRegistry)
#include "tst_qloggingregistry.moc"