Log4Qt 初始化過程

初始化過程

在前面的章節中,我們分享了三種方式來配置 Log4Qt,它們分別是:

的確,這些方式在使用上比較簡單,但是你可曾想過:

要搞明白這些問題,就必須要了解 Log4Qt 的初始化過程。Log4Qt 的初始化分兩個階段進行:

爲了更加清晰的理解這個過程,讓我們再次走進源碼!

階段一****

在靜態初始化期間,會創建 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