Log4Qt 初始化过程

高效程序员

共 22652字,需浏览 46分钟

 ·

2021-08-05 13:36

星标/置顶公众号👇硬核文章第一时间送达


1

初始化过程


在前面的章节中,我们分享了三种方式来配置 Log4Qt,它们分别是:


  • 环境变量 - LOG4QT_DEBUGLOG4QT_DEFAULTINITOVERRIDELOG4QT_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哈哈~!


往期推荐




☞ 专辑 | 趣味设计模式
☞ 专辑 | 音视频开发
☞ 专辑 | C++ 进阶
☞ 专辑 | 超硬核 Qt
☞ 专辑 | 玩转 Linux
☞ 专辑 | GitHub 开源推荐
☞ 专辑 | 程序人生


关注公众「高效程序员」👇一起优秀!

回复“入群”进技术交流群,回复“1024”获取海量学习资源。
浏览 35
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报