Log4Qt 初始化過(guò)程

星標(biāo)/置頂公眾號(hào)??,硬核文章第一時(shí)間送達(dá)!
1
初始化過(guò)程
在前面的章節(jié)中,我們分享了三種方式來(lái)配置 Log4Qt,它們分別是:
環(huán)境變量 - LOG4QT_DEBUG、LOG4QT_DEFAULTINITOVERRIDE、LOG4QT_CONFIGURATION
應(yīng)用程序設(shè)置 - QSettings
默認(rèn)配置文件 - log4qt.properties
的確,這些方式在使用上比較簡(jiǎn)單,但是你可曾想過(guò):
為什么環(huán)境變量會(huì)是 LOG4QT_DEBUG 及其它兩個(gè)?
為什么 QSettings 要有 Log4Qt/Properties 分組?
為什么默認(rèn)的配置文件要是 log4qt.properties 呢?
要搞明白這些問(wèn)題,就必須要了解 Log4Qt 的初始化過(guò)程。Log4Qt 的初始化分兩個(gè)階段進(jìn)行:
階段一:在靜態(tài)初始化期間發(fā)生
階段二:在創(chuàng)建 LogManager 單例時(shí)發(fā)生
為了更加清晰的理解這個(gè)過(guò)程,讓我們?cè)俅巫哌M(jìn)源碼!
2
階段一
在靜態(tài)初始化期間,會(huì)創(chuàng)建 InitialisationHelper 單例。在構(gòu)造過(guò)程中:
InitialisationHelper::InitialisationHelper() :
mStartTime(QDateTime::currentDateTime().toMSecsSinceEpoch())
{
doRegisterTypes();
doInitialiseEnvironmentSettings();
}
它會(huì)使用 Qt 類型系統(tǒng)注冊(cè)一些自定義類型:
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
}
并從系統(tǒng)環(huán)境中讀取所需的值:
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);
}
}
最終,環(huán)境變量的值會(huì)被存儲(chǔ)在 mEnvironmentSettings(QHash)中。
注意:QHash 中的 key 去掉了前綴 log4qt_。例如,LOG4QT_DEBUG 對(duì)應(yīng)的 key 是 Debug。
3
階段二
LogManager 單例是在首次使用時(shí)創(chuàng)建的,這個(gè)創(chuàng)建通常由 Logger 對(duì)象的請(qǐng)求觸發(fā)。Logger::logger() 的調(diào)用被傳遞給 LogManager::logger(),在創(chuàng)建時(shí),LogManager 將創(chuàng)建一個(gè) Hierarchy 對(duì)象作為 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;
}
在創(chuàng)建單例之后,首先會(huì)調(diào)用 LogManager::doConfigureLogLogger() 對(duì) logLogger() 進(jìn)行配置。Level <= INFO 的消息將使用 ConsoleAppender 寫入到 stdout,而 Level >= WARN 的消息則使用第二個(gè) ConsoleAppender 寫入到 stderr。
日志級(jí)別是通過(guò) InitialisationHelper::setting()(key 為 Debug) 從系統(tǒng)環(huán)境或應(yīng)用程序設(shè)置中讀取的,如果找到一個(gè)級(jí)別值,但它不是有效的級(jí)別字符串,則使用 Level::DEBUG_INT。如果沒(méi)有找到級(jí)別字符串,則使用 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));
}
一旦配置了日志,就會(huì)調(diào)用 LogManager::welcome() 來(lái)輸出一些有用的內(nèi)部信息。當(dāng) static_logger() 的日志級(jí)別為 Debug 時(shí),會(huì)輸出程序啟動(dòng)時(shí)間、logLogger() 所使用的日志級(jí)別;而當(dāng)級(jí)別為 Trace 時(shí),則會(huì)輸出環(huán)境變量配置、應(yīng)用程序配置:
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"));
}
}
最后,是調(diào)用 LogManager::doStartup() 來(lái)初始化包,該函數(shù)將使用 InitialisationHelper::setting() 測(cè)試系統(tǒng)環(huán)境和應(yīng)用程序設(shè)置中的 DefaultInitOverride 設(shè)置,如果該值存在并被設(shè)置為任何非 false 的值,初始化將會(huì)被中止。
隨后是獲取 Configuration 的值,如果找到并且是一個(gè)有效的文件路徑,則將會(huì)使用該文件并通過(guò) PropertyConfigurator::configure() 來(lái)配置日志。倘若 Configuration 不可用并且存在 QCoreApplication 對(duì)象,則應(yīng)用程序設(shè)置將針對(duì)組 Log4Qt/Properties 進(jìn)行測(cè)試。如果該組存在,則使用 PropertyConfigurator::configure() 對(duì)日志進(jìn)行配置。倘若配置文件和配置設(shè)置都沒(méi)有被找到,則會(huì)在當(dāng)前工作目錄中搜索 log4qt.properties 文件。如果找到,則使用 PropertyConfigurator::configure() 進(jìn)行配置:
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"));
}
建議:結(jié)合《使用環(huán)境變量配置 Log4Qt》 、《使用 QSettings 配置 Log4Qt》、《使用 log4qt.properties 配置 Log4Qt》來(lái)理解這個(gè)過(guò)程,效果會(huì)更佳!
如果實(shí)在理解不了,就用流程圖把它畫出來(lái),O(∩_∩)O哈哈~!




關(guān)注公眾號(hào)「高效程序員」??,一起優(yōu)秀!
