首页 > 编程语言 >easylogging++的那些事(四)源码分析(十)扩展日志回滚

easylogging++的那些事(四)源码分析(十)扩展日志回滚

时间:2022-12-08 00:01:20浏览次数:64  
标签:std 回滚 level ++ LoggingFlag brief 源码 日志

目录

在上一篇我们介绍了 easylogging++的 异步日志 的实现。在结尾的时候,我们提到 easylogging++的默认实现不符合真实项目的需求,因此日志回滚的功能我们就需要根据实际的项目需求定制一下。今天我们就来看看如何定制日志回滚以满足真实项目的需求。

日志回滚接口

    在 日志格式配置管理类 中我们介绍 TypedConfigurations 类的时候我们详细分析了 easylogging++提供的实现日志回滚的接口:

bool TypedConfigurations::unsafeValidateFileRolling(Level level, const PreRollOutCallback &preRollOutCallback)
{
    base::type::fstream_t *fs = unsafeGetConfigByRef(level, &m_fileStreamMap, "fileStream").get();
    if (fs == nullptr)
    {
        return true;
    }
    std::size_t maxLogFileSize = unsafeGetConfigByVal(level, &m_maxLogFileSizeMap, "maxLogFileSize");
    std::size_t currFileSize = base::utils::File::getSizeOfFile(fs);
    // 判断当前日志文件大小是否达到设定的阈值
    if (maxLogFileSize != 0 && currFileSize >= maxLogFileSize)
    {
        std::string fname = unsafeGetConfigByRef(level, &m_filenameMap, "filename");
        ELPP_INTERNAL_INFO(1, "Truncating log file [" << fname << "] as a result of configurations for level ["
                                                      << LevelHelper::convertToString(level) << "]");
        fs->close();
        // 执行日志滚动回调
        preRollOutCallback(fname.c_str(), currFileSize);
        fs->open(fname, std::fstream::out | std::fstream::trunc);
        return true;
    }
    return false;
}

    但这个接口即使我们实现了提供了日志回滚对应的回调函数 PreRollOutCallback,也没办法满足日常对于日志滚动的需要。我们通过这个回调函数类型的定义就明白了:

typedef std::function<void(const char*, std::size_t)> PreRollOutCallback;

    PreRollOutCallbackvoid(const char*, std::size_t) 这种函数签名的可调用对象,也就是说,可调用对象的参数只能是第一个为 const char*(文件名),第二个参数只能是 std::size_t(文件大小)。很显然,我们是没办法根据这两个参数获取日志文件名的配置项的值(只能通过日志级别来获取),然后动态生成符合日志文件名配置项格式的日志文件名

    日常项目中的日志回滚一般是当日志文件达到一定的条件后,比如文件大小达到阈值或者超过了一定的时间,比如 24 小时,这时候我们会重新生成一个新的日志文件,原日志文件一般保持不变。
    但上面这个接口的默认实现实现的效果却是清空需要当前的日志文件,显然不符合我们真实项目的需求。

    秉着对扩展开放对修改关闭的原则,我们尽量少改动源码。这里,我们只基于文件大小达到阈值(easylogging++的默认日志滚动条件)这个条件实现日志回滚。

扩展后的日志回滚接口

增加创建新文件的 LoggingFlag(CreateNewLogFile)

    这里我们增加一个用于日志滚动时创建新文件的 LoggingFlag(CreateNewLogFile), 枚举类型 LoggingFlag 增加一个值( 这样可以兼容库本身的代码 ):

/// @brief Flags used while writing logs. This flags are set by user
enum class LoggingFlag : base::type::EnumType
{
    /// @brief Makes sure we have new line for each container log entry
    NewLineForContainer = 1,
    /// @brief Makes sure if -vmodule is used and does not specifies a module, then verbose
    /// logging is allowed via that module.
    AllowVerboseIfModuleNotSpecified = 2,
    /// @brief When handling crashes by default, detailed crash reason will be logged as well
    LogDetailedCrashReason = 4,
    /// @brief Allows to disable application abortion when logged using FATAL level
    DisableApplicationAbortOnFatalLog = 8,
    /// @brief Flushes log with every log-entry (performance sensitive) - Disabled by default
    ImmediateFlush = 16,
    /// @brief Enables strict file rolling
    StrictLogFileSizeCheck = 32,
    /// @brief Make terminal output colorful for supported terminals
    ColoredTerminalOutput = 64,
    /// @brief Supports use of multiple logging in same macro, e.g, CLOG(INFO, "default", "network")
    MultiLoggerSupport = 128,
    /// @brief Disables comparing performance tracker's checkpoints
    DisablePerformanceTrackingCheckpointComparison = 256,
    /// @brief Disable VModules
    DisableVModules = 512,
    /// @brief Disable VModules extensions
    DisableVModulesExtensions = 1024,
    /// @brief Enables hierarchical logging
    HierarchicalLogging = 2048,
    /// @brief Creates logger automatically when not available
    CreateLoggerAutomatically = 4096,
    /// @brief Adds spaces b/w logs that separated by left-shift operator
    AutoSpacing = 8192,
    /// @brief Preserves time format and does not convert it to sec, hour etc (performance tracking only)
    FixedTimeFormat = 16384,
    // @brief Ignore SIGINT or crash
    IgnoreSigInt = 32768,
    // @brief When file rolling, if the size of the log file reaches the threshold, a new log file is created
    CreateNewLogFile = 65536,
};

改动后的实现

bool TypedConfigurations::unsafeValidateFileRolling(Level level, const PreRollOutCallback &preRollOutCallback)
{
    base::type::fstream_t *fs = unsafeGetConfigByRef(level, &m_fileStreamMap, "fileStream").get();
    if (fs == nullptr)
    {
        return true;
    }
    std::size_t maxLogFileSize = unsafeGetConfigByVal(level, &m_maxLogFileSizeMap, "maxLogFileSize");
    std::size_t currFileSize = base::utils::File::getSizeOfFile(fs);
    if (maxLogFileSize != 0 && currFileSize >= maxLogFileSize)
    {
        std::string fname = unsafeGetConfigByRef(level, &m_filenameMap, "filename");
        ELPP_INTERNAL_INFO(1, "Truncating log file [" << fname << "] as a result of configurations for level ["
                                                      << LevelHelper::convertToString(level) << "]");
        fs->close();
        preRollOutCallback(fname.c_str(), currFileSize);

        // 日志滚动时创建新文件
        if (ELPP->hasFlag(LoggingFlag::CreateNewLogFile))
        {
            // 1、获取对应日志记录器的Configurations对象
            Configurations *tempConfigurations = const_cast<Configurations *>(configurations());
            if (!tempConfigurations)
            {
                ELPP_INTERNAL_ERROR("Configurations is NULL, please re-check your configurations for level["
                                        << LevelHelper::convertToString(level) << "]",
                                    false);
            }

            // 2、获取对应日志记录器的对应日志级别的日志文件名配置项的字符串值(配置文件中的FILENAME配置项的值,比如FILENAME配置项为FILENAME="log/default-%datetime{%Y%M%d%H%m%s%g}.log"),
            // 那FilenameConfValue值为"log/default-%logger-%level-%datetime{%Y%M%d%H%m%s%g}.log"
            // 之所以这样做,是为了能灵活根据配置项动态调整生成的新的日志文件名称,不是写死的固定格式的日志文件名称,这样更通用
            const std::string &FilenameConfValue = tempConfigurations->get(level, el::ConfigurationType::Filename)->value();
            // 下面的do while循环是为了保证新生成的日志文件名和当前的日志文件名称不同。这里的不足是这里只能支持含时间格式(配置项中含有%datetime)的日志文件名称,比如"log/default-%datetime{%Y%M%d%H%m%s%g}.log"
            const std::string &FilenameConfValue = tempConfigurations->get(level, el::ConfigurationType::Filename)->value();
            std::string resolvedFilename;
            do
            {
                // 调整文件名配置项值中的日期格式部分为实际当前时间,其中的"/"替换为"-"
                resolvedFilename = resolveFilename(FilenameConfValue);
                if (resolvedFilename.empty())
                {
                    std::cerr << "resolveFilename failed! resolvedFilename is empty! please re-check your configurations for level ["
                              << LevelHelper::convertToString(level) << "]";
                }
            } while (resolvedFilename.empty() || (fname == resolvedFilename));
            fname = resolvedFilename;
            // 基于新生成的日志文件名重新打开,相当于对应日志记录器的当前日志级别新创建了一个日志文件,以后写日志文件都写到这个新创建的文件里面
            fs->open(fname, std::fstream::out | std::fstream::trunc);
            if (fs->is_open())
            {
                fs->flush();

                // 日志文件名更新了,文件流对象还是同一个文件流对象,只不过打开的文件变了,这时候需要调整日志记录器对应的日志级别的日志文件相关的映射关系
                // 这里的映射关系主要是有三个:
                // m_filenameMap:日志级别和日志文件名的映射关系(日志文件名改变了,需要调整)
                // m_logStreamsReference:日志文件名和文件流的映射关系(日志文件名改变了,需要调整)
                // m_fileStreamMap:日志级别和文件流的映射关系(文件流对象并未改变,不需要调整)
                setValue(level, fname, &m_filenameMap);
                m_logStreamsReference->insert(std::make_pair(fname, base::FileStreamPtr(m_fileStreamMap.at(level))));
            }
            else
            {
                ELPP_INTERNAL_ERROR("Bad file [" << fname << "]", true);
            }
        }
        else
        {
            // 源码当中只是简单的清空日志文件后重新打开
            fs->open(fname, std::fstream::out | std::fstream::trunc);
        }
        return true;
    }
    return false;
}

    实际使用日志滚动时,需要在程序入口处增加两个 LoggingFlag
    1) 开启日志滚动的 StrictLogFileSizeCheck
    2) 日志滚动时,当文件大小达到阈值时创建新文件的 CreateNewLogFile

    程序入口如下:

#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP

int main(int argc, char *argv[])
{
    el::Loggers::addFlag(el::LoggingFlag::StrictLogFileSizeCheck);
    el::Loggers::addFlag(el::LoggingFlag::CreateNewLogFile);

    return 0;
}

至此,日志滚动的修改就完成了。
到目前为止,我们已经将 easylogging++的主要功能的实现基本都分析完了。从下一篇开始,我们将前面未分析过的类或者接口一一介绍。

标签:std,回滚,level,++,LoggingFlag,brief,源码,日志
From: https://www.cnblogs.com/DesignLife/p/16964935.html

相关文章

  • C++学习---cstdio的源码学习分析01-类型定义
    引言cstdio文件是C++对stdio.h头文件的封装,StandardInputandOutputLibrary,定义了一系列标准输入输出函数,包括文件操作(fopen/fclose等),格式化打印(printf/scanf)等。通......
  • C++入门级基础知识汇总
    知识来源:https://www.imooc.com/learn/1304https://www.runoob.com/cplusplus/cpp-tutorial.html 编程第一步导入头文件:#include<stdio.h>std=standard......
  • 安卓APP源码和设计报告——健身系统
    一、设计背景1.需求分析对于很多人来说拥有一副好身材能让自己增添不少魅力;对于爱吃而又担心自己发胖的人来说适当的运动健身是最好的选择。移动互联网时代,市场上“约跑”......
  • 安卓APP源码和设计报告——小说阅读器
    班级姓名学号答辩情况考核项满分成绩得分掌握计算机系统软硬件资源管理的原理,能够设计针对计算机领域复杂工程问题的解决方案,设计满足特定需求的软硬件系统,并具有对解决方......
  • easylogging++的那些事(四)源码分析(九)异步日志
    目录异步日志是什么?异步日志相关的类AsyncLogItem类AsyncLogQueue类AsyncLogDispatchCallback类IWorker类AsyncDispatchWorker类构造函数析构函数启动异步日志写日志......
  • C++
    通讯录管理系统1、系统需求通讯录是一个可以记录亲人、好友信息的工具。本教程主要利用C++来实现一个通讯录管理系统系统中需要实现的功能如下:添加联系人:向通讯录中......
  • 备忘录APP源码和设计报告
    大作业文档项目名称:备忘录专业:班级:1学号:姓名:目 录一、项目功能介绍3二、项目运行环境31、开发环境32、运行环境33、是否需要联网3三、项目配置文件及工程结构31、工程配置......
  • 安卓APP源码和设计报告——运动健身教学
    实验报告课程名称实验名称指导教师专业班级学号姓名目录一、设计背景31.需求分析32.课题研究的目的和意义3二、系统需求分析与开发环境31.系统功能需求32.系统界面需......
  • 鞋子商店APP源码和设计报告
    实验报告课程名称实验名称指导教师专业班级学号姓名一、需求分析1.需求分析随着互联网和手机技术的蓬勃发展,网购已经成为许多人,尤其是年轻人的主要消费方式,这就对手机购......
  • 新闻APP(娱乐)源码和设计报告
    项目名称:新闻娱乐APP专业:班级:学号:姓名:目 录一、项目功能介绍3二、项目运行环境31、开发环境32、运行环境33、是否需要联网3三、项目配置文件及工程结构31、工程配置文件32......