Log4Qt 初始化過程
初始化過程
在前面的章節中,我們分享了三種方式來配置 Log4Qt,它們分別是:
-
環境變量 - LOG4QT_DEBUG、LOG4QT_DEFAULTINITOVERRIDE、LOG4QT_CONFIGURATION
-
應用程序設置 - QSettings
-
默認配置文件 - log4qt.properties
的確,這些方式在使用上比較簡單,但是你可曾想過:
-
爲什麼環境變量會是 LOG4QT_DEBUG 及其它兩個?
-
爲什麼 QSettings 要有 Log4Qt/Properties 分組?
-
爲什麼默認的配置文件要是 log4qt.properties 呢?
要搞明白這些問題,就必須要了解 Log4Qt 的初始化過程。Log4Qt 的初始化分兩個階段進行:
-
階段一:在靜態初始化期間發生
-
階段二:在創建 LogManager 單例時發生
爲了更加清晰的理解這個過程,讓我們再次走進源碼!
階段一****
在靜態初始化期間,會創建 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。
階段二****
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 哈哈~!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/fvfhTzSTx-8jlgncyHALzg