Log4Qt 初始化过程
星标/置顶公众号👇,硬核文章第一时间送达!
1
初始化过程
在前面的章节中,我们分享了三种方式来配置 Log4Qt,它们分别是:
环境变量 - LOG4QT_DEBUG、LOG4QT_DEFAULTINITOVERRIDE、LOG4QT_CONFIGURATION
应用程序设置 - QSettings
默认配置文件 - log4qt.properties
的确,这些方式在使用上比较简单,但是你可曾想过:
为什么环境变量会是 LOG4QT_DEBUG 及其它两个?
为什么 QSettings 要有 Log4Qt/Properties 分组?
为什么默认的配置文件要是 log4qt.properties 呢?
要搞明白这些问题,就必须要了解 Log4Qt 的初始化过程。Log4Qt 的初始化分两个阶段进行:
阶段一:在静态初始化期间发生
阶段二:在创建 LogManager 单例时发生
为了更加清晰的理解这个过程,让我们再次走进源码!
2
阶段一
在静态初始化期间,会创建 InitialisationHelper 单例。在构造过程中:
InitialisationHelper::InitialisationHelper() :
mStartTime(QDateTime::currentDateTime().toMSecsSinceEpoch())
{
doRegisterTypes();
doInitialiseEnvironmentSettings();
}
它会使用 Qt 类型系统注册一些自定义类型:
void InitialisationHelper::doRegisterTypes()
{
qRegisterMetaType<Log4Qt::LogError>("Log4Qt::LogError");
qRegisterMetaType<Log4Qt::Level>("Log4Qt::Level");
qRegisterMetaType<Log4Qt::LoggingEvent>("Log4Qt::LoggingEvent");
#ifndef QT_NO_DATASTREAM
#if QT_VERSION < 0x060000
qRegisterMetaTypeStreamOperators<Log4Qt::LogError>("Log4Qt::LogError");
qRegisterMetaTypeStreamOperators<Log4Qt::Level>("Log4Qt::Level");
qRegisterMetaTypeStreamOperators<LoggingEvent>("Log4Qt::LoggingEvent");
#endif
#endif
}
并从系统环境中读取所需的值:
void InitialisationHelper::doInitialiseEnvironmentSettings()
{
// Is Process::systemEnvironment() safe to be used before a QCoreApplication
// object has been created?
QStringList setting_keys;
setting_keys << QStringLiteral("Debug");
setting_keys << QStringLiteral("DefaultInitOverride");
setting_keys << QStringLiteral("Configuration");
QHash<QString, QString> env_keys;
for (const auto &entry : qAsConst(setting_keys))
env_keys.insert(QStringLiteral("log4qt_").append(entry).toUpper(), entry);
QStringList sys_env = QProcess::systemEnvironment();
for (const auto &entry : qAsConst(sys_env))
{
int i = entry.indexOf(QLatin1Char('='));
if (i == -1)
continue;
QString key = entry.left(i);
QString value = entry.mid(i + 1).trimmed();
if (env_keys.contains(key))
mEnvironmentSettings.insert(env_keys.value(key), value);
}
}
最终,环境变量的值会被存储在 mEnvironmentSettings(QHash)中。
注意:QHash 中的 key 去掉了前缀 log4qt_。例如,LOG4QT_DEBUG 对应的 key 是 Debug。
3
阶段二
LogManager 单例是在首次使用时创建的,这个创建通常由 Logger 对象的请求触发。Logger::logger() 的调用被传递给 LogManager::logger(),在创建时,LogManager 将创建一个 Hierarchy 对象作为 logger repository:
LogManager::LogManager() :
#if QT_VERSION < 0x050E00
mObjectGuard(QMutex::Recursive), // Recursive for doStartup() to call doConfigureLogLogger()
#endif
mLoggerRepository(new Hierarchy()),
mHandleQtMessages(false),
mWatchThisFile(false),
mQtMsgHandler(nullptr)
{
}
LogManager *LogManager::instance()
{
// Do not use Q_GLOBAL_STATIC. The LogManager is rather expensive
// to construct, an exit handler must be set and doStartup must be
// called.
if (!mInstance)
{
QMutexLocker locker(singleton_guard());
if (!mInstance)
{
mInstance = new LogManager;
atexit(shutdown);
mInstance->doConfigureLogLogger();
mInstance->welcome();
mInstance->doStartup();
}
}
return mInstance;
}
在创建单例之后,首先会调用 LogManager::doConfigureLogLogger() 对 logLogger() 进行配置。Level <= INFO 的消息将使用 ConsoleAppender 写入到 stdout,而 Level >= WARN 的消息则使用第二个 ConsoleAppender 写入到 stderr。
日志级别是通过 InitialisationHelper::setting()(key 为 Debug) 从系统环境或应用程序设置中读取的,如果找到一个级别值,但它不是有效的级别字符串,则使用 Level::DEBUG_INT。如果没有找到级别字符串,则使用 Level::ERROR_INT:
void LogManager::doConfigureLogLogger()
{
QMutexLocker locker(&instance()->mObjectGuard);
// Level
QString value = InitialisationHelper::setting(QStringLiteral("Debug"),
QStringLiteral("ERROR"));
logLogger()->setLevel(OptionConverter::toLevel(value, Level::DEBUG_INT));
// Common layout
LayoutSharedPtr p_layout(new TTCCLayout());
p_layout->setName(QStringLiteral("LogLog TTCC"));
static_cast<TTCCLayout *>(p_layout.data())->setContextPrinting(false);
p_layout->activateOptions();
// Common deny all filter
FilterSharedPtr p_denyall(new DenyAllFilter());
p_denyall->activateOptions();
// ConsoleAppender on stdout for all events <= INFO
ConsoleAppender *p_appender;
p_appender = new ConsoleAppender(p_layout, ConsoleAppender::STDOUT_TARGET);
auto *pFilterStdout = new LevelRangeFilter();
pFilterStdout->setNext(p_denyall);
pFilterStdout->setLevelMin(Level::NULL_INT);
pFilterStdout->setLevelMax(Level::INFO_INT);
pFilterStdout->activateOptions();
p_appender->setName(QStringLiteral("LogLog stdout"));
p_appender->addFilter(FilterSharedPtr(pFilterStdout));
p_appender->activateOptions();
logLogger()->addAppender(AppenderSharedPtr(p_appender));
// ConsoleAppender on stderr for all events >= WARN
p_appender = new ConsoleAppender(p_layout, ConsoleAppender::STDERR_TARGET);
auto *pFilterStderr = new LevelRangeFilter();
pFilterStderr->setNext(p_denyall);
pFilterStderr->setLevelMin(Level::WARN_INT);
pFilterStderr->setLevelMax(Level::OFF_INT);
pFilterStderr->activateOptions();
p_appender->setName(QStringLiteral("LogLog stderr"));
p_appender->addFilter(FilterSharedPtr(pFilterStderr));
p_appender->activateOptions();
logLogger()->addAppender(AppenderSharedPtr(p_appender));
}
一旦配置了日志,就会调用 LogManager::welcome() 来输出一些有用的内部信息。当 static_logger() 的日志级别为 Debug 时,会输出程序启动时间、logLogger() 所使用的日志级别;而当级别为 Trace 时,则会输出环境变量配置、应用程序配置:
void LogManager::welcome()
{
static_logger()->info(QStringLiteral("Initialising Log4Qt %1"),
QStringLiteral(LOG4QT_VERSION_STR));
// Debug: Info
if (static_logger()->isDebugEnabled())
{
// Create a nice timestamp with UTC offset
DateTime start_time = QDateTime::fromMSecsSinceEpoch(InitialisationHelper::startTime());
QString offset;
{
QDateTime utc = start_time.toUTC();
QDateTime local = start_time.toLocalTime();
QDateTime local_as_utc = QDateTime(local.date(), local.time(), Qt::UTC);
int min = utc.secsTo(local_as_utc) / 60;
if (min < 0)
offset += QLatin1Char('-');
else
offset += QLatin1Char('+');
min = abs(min);
offset += QString::number(min / 60).rightJustified(2, QLatin1Char('0'));
offset += QLatin1Char(':');
offset += QString::number(min % 60).rightJustified(2, QLatin1Char('0'));
}
static_logger()->debug(QStringLiteral("Program startup time is %1 (UTC%2)"),
start_time.toString(QStringLiteral("ISO8601")),
offset);
static_logger()->debug(QStringLiteral("Internal logging uses the level %1"),
logLogger()->level().toString());
}
// Trace: Dump settings
if (static_logger()->isTraceEnabled())
{
static_logger()->trace(QStringLiteral("Settings from the system environment:"));
auto settings = InitialisationHelper::environmentSettings();
for (auto pos = std::begin(settings);pos != std::end(settings);++pos)
static_logger()->trace(QStringLiteral(" %1: '%2'"), pos.key(), pos.value());
static_logger()->trace(QStringLiteral("Settings from the application settings:"));
if (QCoreApplication::instance())
{
const QLatin1String log4qt_group("Log4Qt");
const QLatin1String properties_group("Properties");
static_logger()->trace(QStringLiteral(" %1:"), log4qt_group);
QSettings s;
s.beginGroup(log4qt_group);
for (const auto &entry : s.childKeys())
static_logger()->trace(QStringLiteral(" %1: '%2'"),
entry,
s.value(entry).toString());
static_logger()->trace(QStringLiteral(" %1/%2:"), log4qt_group, properties_group);
s.beginGroup(properties_group);
for (const auto &entry : s.childKeys())
static_logger()->trace(QStringLiteral(" %1: '%2'"),
entry,
s.value(entry).toString());
}
else
static_logger()->trace(QStringLiteral(" QCoreApplication::instance() is not available"));
}
}
最后,是调用 LogManager::doStartup() 来初始化包,该函数将使用 InitialisationHelper::setting() 测试系统环境和应用程序设置中的 DefaultInitOverride 设置,如果该值存在并被设置为任何非 false 的值,初始化将会被中止。
随后是获取 Configuration 的值,如果找到并且是一个有效的文件路径,则将会使用该文件并通过 PropertyConfigurator::configure() 来配置日志。倘若 Configuration 不可用并且存在 QCoreApplication 对象,则应用程序设置将针对组 Log4Qt/Properties 进行测试。如果该组存在,则使用 PropertyConfigurator::configure() 对日志进行配置。倘若配置文件和配置设置都没有被找到,则会在当前工作目录中搜索 log4qt.properties 文件。如果找到,则使用 PropertyConfigurator::configure() 进行配置:
void LogManager::doStartup()
{
QMutexLocker locker(&instance()->mObjectGuard);
// Override
QString default_value = QStringLiteral("false");
QString value = InitialisationHelper::setting(QStringLiteral("DefaultInitOverride"),
default_value);
if (value != default_value)
{
static_logger()->debug(QStringLiteral("DefaultInitOverride is set. Aborting default initialisation"));
return;
}
// Configuration using setting Configuration
value = InitialisationHelper::setting(QStringLiteral("Configuration"));
if (!value.isEmpty() && QFile::exists(value))
{
static_logger()->debug(QStringLiteral("Default initialisation configures from file '%1' specified by Configure"), value);
PropertyConfigurator::configure(value);
return;
}
const QString default_file(QStringLiteral("log4qt.properties"));
QStringList filesToCheck;
// Configuration using setting
if (auto app = QCoreApplication::instance())
{
Q_UNUSED(app)
const QLatin1String log4qt_group("Log4Qt");
const QLatin1String properties_group("Properties");
QSettings s;
s.beginGroup(log4qt_group);
if (s.childGroups().contains(properties_group))
{
static_logger()->debug(QStringLiteral("Default initialisation configures from setting '%1/%2'"), log4qt_group, properties_group);
s.beginGroup(properties_group);
PropertyConfigurator::configure(s);
return;
}
// Configuration using executable file name + .log4qt.properties
QString binConfigFile = QCoreApplication::applicationFilePath() + QLatin1Char('.') + default_file;
filesToCheck << binConfigFile;
if (binConfigFile.contains(QLatin1String(".exe."), Qt::CaseInsensitive))
{
binConfigFile.replace(QLatin1String(".exe."), QLatin1String("."), Qt::CaseInsensitive);
filesToCheck << binConfigFile;
}
filesToCheck << QFileInfo(QCoreApplication::applicationFilePath()).path() + QLatin1Char('/') + default_file;
}
filesToCheck << default_file;
for (const auto &configFileName: qAsConst(filesToCheck))
{
// Configuration using default file
if (QFile::exists(configFileName))
{
static_logger()->debug(QStringLiteral("Default initialisation configures from default file '%1'"), configFileName);
PropertyConfigurator::configure(configFileName);
if (mWatchThisFile)
ConfiguratorHelper::setConfigurationFile(configFileName, PropertyConfigurator::configure);
return;
}
}
static_logger()->debug(QStringLiteral("Default initialisation leaves package unconfigured"));
}
建议:结合《使用环境变量配置 Log4Qt》 、《使用 QSettings 配置 Log4Qt》、《使用 log4qt.properties 配置 Log4Qt》来理解这个过程,效果会更佳!
如果实在理解不了,就用流程图把它画出来,O(∩_∩)O哈哈~!
关注公众号「高效程序员」👇,一起优秀!