[C++]——同步异步日志系统(5)

news2024/9/22 11:28:06

同步异步日志系统

  • 一、日志消息格式化设计
    • 1.1 格式化子项类的定义和实现
    • 1.2 格式化类的定义和实现
  • 二、日志落地类设计
    • 2.1 日志落地模块功能实现与测试
    • 2.2 日志落地模块功能功能扩展

一、日志消息格式化设计

  1. 日志格式化模块的作用:对日志消息进行格式化,并且组织成指定格式的字符串。

%d ⽇期
%T 缩进
%t 线程id
%p ⽇志级别
%c ⽇志器名称
%f ⽂件名
%l ⾏号
%m ⽇志消息
%n 换⾏
如:[2024-07-09 17:04][root][1234567][main.c:99][FATAL]:\t创建套接字失败…\n

格式化字符串控制了日志的输出格式
定义格式化字符,是为了让日志系统进行日志格式化更加的灵活方便。

成员:
1.格式化字符串(用户定义的输出格式格式)
2.格式化子项数组(对格式化字符串进行解析,保存了日志消息要素的排序)
不同的格式化子项,会从日志消息中取出指定的元素,转化为字符串。
[%d{%H:%M:%S}][%f:%l]%m%n

格式化子项:

其他信息(非格式化字符)子项:[
日期子项:%H%M%S
其他信息子项:]
其他信息子项:[
文件名子项:main.c
其他信息子项::
行号信息子项:99
其他信息子项:]
消息主体子项:吃饭睡觉打豆豆
换行子项:\n

[12:40;50][main.c:99]吃饭睡觉打豆豆\n

1.1 格式化子项类的定义和实现

  1. 格式化子项的实现思想:从日志消息中取出指定的元素,追加到一块内存空间中。
    设计思想:
    1.抽象出一个格式化子项的基类
    2.基于基类,派生出不同的格式化子项子类:
    主体消息、日志等级、时间子项、文件名、行号、日志器名称、线程ID、制表符、换行、非格式化的原始字符串。
    这样就可以在父类中定义父类指针的数组,指向不同的格式化子项子类的对象。

FormatItem类主要负责日志消息子项的获取及格式化。其包含以下子类:

  • MsgFormatItem :表示要从LogMsg中取出有效⽇志数据
  • LevelFormatItem:表示要从LogMsg中取出⽇志等级
  • NameFormatItem :表示要从LogMsg中取出⽇志器名称
  • ThreadFormatItem :表示要从LogMsg中取出线程ID
  • TimeFormatItem:表示要从LogMsg中取出时间戳并按照指定格式进行格式化
  • CFileFormatItem :表示要从LogMsg中取出源码所在⽂件名
  • CLineFormatItem :表示要从LogMsg中取出源码所在⾏号
  • TabFormatItem :表示⼀个制表符缩进
  • NLineFormatItem :表示⼀个换行
  • OtherFormatItem :表示⾮格式化的原始字符串
  1. 首先搭架子,定义抽象的格式化子项的基类
  //抽象格式化子类基类
    class FormatItem{
        //c++17语法与typedef作用一样
        using ptr=std::shared_ptr<FormatItem>;
        //纯虚函数
        virtual void format(std::ostream &out,const LogMsg &msg)=0;
    }
  1. 在基类的基础上,派生出格式化子项的子类
#ifndef __M_FORMAT_H__
#define __M_FORMAT_H__
//日志消息格式化模块
#include <ctime>
#include "level.hpp"
#include "message.hpp"

namespace logslearn{
    //抽象格式化子类基类
    class FormatItem{
        //c++17语法与typedef作用一样
        using ptr=std::shared_ptr<FormatItem>;
        //纯虚函数
        virtual void format(std::ostream &out,const LogMsg &msg)=0;
    };

    //派生出格式化子项的子类:主体消息、日志等级、时间子项、文件名、行号、日志器名称、线程ID、制表符、换行、非格式化的原始字符串
    //主体消息
    class MsgFormatItem:public FormatItem{
        public:
        //虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override{
            out<<msg._payload;
        }
    };
    //日志等级
    class LevelFormatItem:public FormatItem{
        public:
        //虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override{
            out<<loglevel::tostring(msg._level);
        }
    };

     //时间子项
    class TimeFormatItem:public FormatItem{
        public:
        //默认构造函数,设置时间的默认格式
        TimeFormatItem(const std::string &fmt="%H:%M:%S"):_time_fmt(fmt){}
        //虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override{
            struct tm t;
            localtime_r(&msg._ctime,&t);
            char tmp[32]={0};
            strftime(tmp,31,_time_fmt.c_str(),&t);
            out<<tmp;
        }
        private:
        std::string _time_fmt;//默认的时间格式
    };

     //文件名
    class FileFormatItem:public FormatItem{
        public:
        //虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override{
            out<<msg._file;
        }
    };

     //行号
    class LineFormatItem:public FormatItem{
        public:
        //虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override{
            out<<msg._line;
        }
    };

     //日志器名称
    class LoggerFormatItem:public FormatItem{
        public:
        //虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override{
            out<<msg._logger;
        }
    };

     //线程ID
    class ThreadFormatItem:public FormatItem{
        public:
        //虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override{
            out<<msg._tid;
        }
    };

     //制表符
    class TabFormatItem:public FormatItem{
        public:
        //虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override{
            out<<"\t";
        }
    };

     //换行
    class NLineFormatItem:public FormatItem{
        public:
        //虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override{
            out<<"\n";
        }
    };

     //非格式化的原始字符串
    class OtherFormatItem:public FormatItem{
        public:
        //设置默认构造函数
        OtherFormatItem(std::string &str):_str(str){}
        //虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override{
            out<<_str;
        }
        private:
        std::string _str;
    };
}
#endif

1.2 格式化类的定义和实现

  1. 确定框架,设计格式化类,设计需要的成员,需要完成的功能。
/*
 格式化类的定义和实现
 %d 表示日期 ,包含子格式{%H%M%S} 
 %t 表示线程id
 %c 表示⽇志器名称
 %f 表示源码⽂件名
 %l 表示源码⾏号
 %p 表示⽇志级别
 %T 表示制表符缩进
 %m 表示主体消息
 %n 表示换⾏
*/
class Formatter{
    public:
        //构造默认函数
        Formatter(const std::string &pattern="[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n"):_pattern(pattern){}
        //对msg进行格式化
        void format(std::ostream &out,const LogMsg &msg);
        std::string format(); 
        //对格式化规则字符进行解析
        bool parsePatern();
    private:
        //根据不同的格式化字符创建不同的格式化子项对象
        FormatItem::ptr createItem(const std::string &key,const std::string &val);
    private:
        std::string _pattern;//格式化规则字符串
        std::vector<logslearn::FormatItem::ptr> _items;//格式化字符串解析出的格式化子项
};
  1. 对格式化的功能接口进行设计
#ifndef __M_FORMAT_H__
#define __M_FORMAT_H__
// 日志消息格式化模块

#include "message.hpp"
#include "level.hpp"
#include <memory>
#include <ctime>
#include <vector>
#include <assert.h>
#include <sstream>
namespace logslearn
{
    // 抽象格式化子类基类
    class FormatItem
    {
    public:
        // c++17语法与typedef作用一样
        using ptr = std::shared_ptr<FormatItem>;
        // 纯虚函数
        virtual void format(std::ostream &out,const LogMsg &msg) = 0;
    };

    // 派生出格式化子项的子类:主体消息、日志等级、时间子项、文件名、行号、日志器名称、线程ID、制表符、换行、非格式化的原始字符串
    // 主体消息
    class MsgFormatItem : public FormatItem
    {
    public:
        // 虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override
        {
            out << msg._payload;
        }
    };
    // 日志等级
    class LevelFormatItem : public FormatItem
    {
    public:
        // 虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override
        {
            out << loglevel::tostring(msg._level);
        }
    };

    // 时间子项
    class TimeFormatItem : public FormatItem
    {
    public:
        // 默认构造函数,设置时间的默认格式
        TimeFormatItem(const std::string &fmt = "%H:%M:%S") : _time_fmt(fmt) {
            if (_time_fmt.empty()) _time_fmt = "%H:%M:%S";
        }
        // 虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override
        {
            struct tm t;
            localtime_r(&msg._ctime, &t);
            char tmp[32] = {0};
            strftime(tmp, 31, _time_fmt.c_str(), &t);
            out<<tmp;
        }

    private:
        std::string _time_fmt; // 默认的时间格式
    };

    // 文件名
    class FileFormatItem : public FormatItem
    {
    public:
        // 虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override
        {
            out << msg._file;
        }
    };

    // 行号
    class LineFormatItem : public FormatItem
    {
    public:
        // 虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override
        {
            out << msg._line;
        }
    };

    // 日志器名称
    class LoggerFormatItem : public FormatItem
    {
    public:
        // 虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override
        {
            out << msg._logger;
        }
    };

    // 线程ID
    class ThreadFormatItem : public FormatItem
    {
    public:
        // 虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override
        {
            out << msg._tid;
        }
    };

    // 制表符
    class TabFormatItem : public FormatItem
    {
    public:
        // 虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override
        {
            out << "\t";
        }
    };

    // 换行
    class NLineFormatItem : public FormatItem
    {
    public:
        // 虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override
        {
            out << "\n";
        }
    };

    // 非格式化的原始字符串
    class OtherFormatItem : public FormatItem
    {
    public:
        // 设置默认构造函数
        OtherFormatItem(std::string str) : _str(str) {}
        // 虚函数进行重写
        void format(std::ostream &out,const LogMsg &msg) override
        {
            out << _str;
        }

    private:
        std::string _str;
    };
    /*
     格式化类的定义和实现
     %d 表示日期 ,包含子格式{%H%M%S}
     %t 表示线程id
     %c 表示⽇志器名称
     %f 表示源码⽂件名
     %l 表示源码⾏号
     %p 表示⽇志级别
     %T 表示制表符缩进
     %m 表示主体消息
     %n 表示换⾏
    */
    class Formatter
    {
    public:
    //基类指针,用来控制继承子类的对象
        using ptr=std::shared_ptr<Formatter>;
        // 构造默认函数
        Formatter(const std::string &pattern = "[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n") : _pattern(pattern)
        {
            // 断言是否解析格式化规则字符
            assert(parsePatern());
        }
        // 对msg进行格式化
        void format(std::ostream &out, const LogMsg &msg)
        {
            for (auto &item : _items)
            {
                item->format(out, msg);
            }
        }
        std::string format(LogMsg &msg)
        {
            std::stringstream ss;
            format(ss, msg);
            return ss.str();
        }
        private:
        // 对格式化字符进行解析
        bool parsePatern()
        {
            // 1.对格式化规则字符串进行解析
            // 2.根据解析得到的数据初始化格式化子项数组成员
            // 规则字符串的处理过程是一个循环的过程,原始字符串结束后,遇到%,则处理一个格式化字符
            std::vector<std::pair<std::string, std::string>> fmt_order;
            size_t pos = 0;
            std::string key, val;
            while (pos < _pattern.size())
            {
                // 1.处理原始字符串--判断是否是%,不是就是原始字符串
                if (_pattern[pos] != '%')
                {
                    val.push_back(_pattern[pos++]);
                    continue;
                }
                // 能走下来就代表pos位置就是%字符,%%处理称为一个原始%字符
                if (pos + 1 < _pattern.size() && _pattern[pos + 1] == '%')
                {
                    val.push_back('%');
                    pos += 2;
                    continue;
                }
                // 能走下去,代表%后面是格式化字符,代表原始字符串处理完毕
                if (val.empty() == false)
                {
                    fmt_order.push_back(std::make_pair("", val));
                    val.clear(); // 清空
                }

                // 这时候pos指向的是%的位置,是格式化字符的处理
                pos += 1; // 这一步之后,pos位置指向格式化字符的位置
                if (pos == _pattern.size())
                {
                    std::cout << "%之后没有对应的格式化字符!\n";
                    return false;
                }
                key = _pattern[pos];
                // 这时候pos指向格式化字符后的位置
                pos += 1;
                if (pos < _pattern.size() && _pattern[pos] == '{')
                {
                    // 这时候pos指向{之后,子规则的起始位置
                    pos += 1;
                    while (pos < _pattern.size() && _pattern[pos] != '}')
                    {
                        val.push_back(_pattern[pos++]);
                    }
                    // 走到末尾跳出循环,则代表没有遇到},代表格式是错误的
                    if (pos == _pattern.size())
                    {
                        std::cout << "子规则{}匹配出错!\n";
                        return false; // 没有找到}
                    }
                    pos += 1; // 因为这时候pos指向的是}位置,向后走一步,走到了下次处理的新位置
                }
                fmt_order.push_back(std::make_pair(key, val)); // 添加处理的结果
                // 两次都清空,开始下一次处理
                key.clear();
                val.clear();
            }
            // 2.根据解析得到的数据初始化格式化子项数组成员
            for (auto &it : fmt_order)
            {
                _items.push_back(createItem(it.first, it.second));
            }
            return true;
        }

        // 根据不同的格式化字符创建不同的格式化子项对象
        FormatItem::ptr createItem(const std::string &key, const std::string &val)
        {
            if (key == "d")
                return std::make_shared<TimeFormatItem>(val);
            if (key == "t")
                return std::make_shared<ThreadFormatItem>();
            if (key == "c")
                return std::make_shared<LoggerFormatItem>();
            if (key == "f")
                return std::make_shared<FileFormatItem>();
            if (key == "l")
                return std::make_shared<LineFormatItem>();
            if (key == "p")
                return std::make_shared<LevelFormatItem>();
            if (key == "T")
                return std::make_shared<TabFormatItem>();
            if (key == "m")
                return std::make_shared<MsgFormatItem>();
            if (key == "n")
                return std::make_shared<NLineFormatItem>();
            if (key == "")
                return std::make_shared<OtherFormatItem>(val);
            std::cout << "没有对应的格式化字符串:%" << key << std::endl;
            abort();
            return FormatItem::ptr();
        }

    private:
        std::string _pattern;                           // 格式化规则字符串
        std::vector<logslearn::FormatItem::ptr> _items; // 格式化字符串解析出的格式化子项
    };
}
#endif
  1. 对日志格式化模块进行测试和完善
    1)日志格式化的默认格式
//测试代码
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "format.hpp"
int main()
{
   
    //日志格式化模块测试
    logslearn::LogMsg msg(logslearn::loglevel::value::INFO,53,"main.c","root","格式化功能测试...");
    logslearn::Formatter fmt;//不给格式会生成默认格式
    std::string str=fmt.format(msg);
    std::cout<<str;
    //std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;
    return 0;
}

在这里插入图片描述
2)对日志进行边缘测试
测试了三种情况

//测试代码
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "format.hpp"
int main()
{
    //日志格式化模块测试
    //边缘测试
    logslearn::LogMsg msg(logslearn::loglevel::value::INFO,53,"main.c","root","格式化功能测试...");
    //logslearn::Formatter fmt("abc%%abc[%d{%H:%M:%S}]%m%n");//1.测试%
     //logslearn::Formatter fmt("abc%%abc[%d{%H:%M:%S}]%m%n{");//2.测试子项的{}
    logslearn::Formatter fmt("abc%%abc[%d{%H:%M:%S}]%m%");//3.%后的字符
    std::string str=fmt.format(msg);
    std::cout<<str;
    return 0;    
}

在这里插入图片描述

二、日志落地类设计

功能:将格式化完成后的日志消息字符串,输出到指定位置。(支持同时将日志落地到不同位置)
位置分类:
1.标准输出
2.指定文件(事后进行日志分析)
3.滚动文件(文件按时间/大小进行滚动切换)

滚动⽇志⽂件输出的必要性:
由于机器磁盘空间有限, 我们不可能⼀直⽆限地向⼀个⽂件中增加数据
如果⼀个⽇志⽂件体积太⼤,⼀⽅⾯是不好打开,另⼀⽅⾯是即时打开了由于包含数据巨 ⼤,也不利于查找我们需要的信息
所以实际开发中会对单个⽇志⽂件的⼤⼩也会做⼀些控制,即当⼤⼩超过某个⼤⼩时(如
1MB),我们就重新创建⼀个新的⽇志⽂件来滚动写⽇志。 对于那些过期的⽇志, ⼤部分企业内部都有专⻔的运维⼈员去定时清理过期的⽇志,或者设置系统定时任务,定时清理过期⽇志。
⽇志⽂件的滚动思想:
⽇志⽂件滚动的条件有两个:⽂件⼤⼩和时间.
我们可以选择:
▪ ⽇志⽂件在⼤于 1MB 的时候会更换新的⽂件
▪ 每天定点滚动⼀个⽇志⽂件 本项⽬基于⽂件⼤⼩的判断滚动⽣成新的⽂件

扩展:支持落地方向的扩展
用户可以自己编写一个新的落地模块,将日志进行其他方向的落地
实现思想:
1.抽象出落地模块类
2.不同落地方向从基类进行派生(使用基类指针,指向子类对象,就可以调用子类对象的接口进行扩展)
3.使用工厂模式进行创建与表示的分离

2.1 日志落地模块功能实现与测试

  1. 第一步先要设计日志落地的模块,把大致的框架建好。
/*日志落地模块的实现
1.抽象落地基类
2.派生子类(根据不同的落地方向进行派生)
3.使用工厂模式进行创建与表示分离
*/
#ifndef __M_SINK_H__
#define __M_SINK_H__
#include "util.hpp"
#include <fstream>
#include <memory>
namespace logslearn
{
    // 抽象落地基类
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink() {}
        virtual ~LogSink() {}
        // 纯虚函数,日志落地功能
        virtual void log(const char *data, size_t len) = 0;
    };
    // 落地方向:标准输出
    class StdoutSink : public LogSink
    {
    public:
        // 将日志消息写入到标准输出
        void log(const char *data, size_t len);
    };
    // 落地方向:指定文件
    class FileSink : public LogSink
    {
    public:
        // 构造时存入文件名,并打开文件,将操作句柄管理起来
        FileSink(const std::string &pathname);
        // 将日志消息写入到指定文件
        void log(const char *data, size_t len);
    private:
        std::string _pathname;
        std::ofstream _ofs;
    };
    // 落地方向:滚动文件(以大小进行滚动)
    class RoolBySizeSink : public LogSink
    {
    public:
        // 构造时存入文件名,并打开文件,将操作句柄管理起来
        RoolBySizeSink(const std::string &basename,size_t max_fsize);//需要用户告知,基础的文件名和文件大小
        // 将日志消息写入到标准输出--写入前判断文件大小,超过了最大大小就要切换文件
        void log(const char *data, size_t len);
        private:
        //创建一个新文件,不需要用户去创建,所有我们把权限设置为私有
        void createNewFile();//进行大小判断,超过指定大小则需要创建新文件
    private:
            //通过基础文件名+扩展文件名(以时间生成)组成一个实际的当前输出文件名
            size_t _name_count;//名称计数器
            std::string _basename;//文件的基础名字如./logs/base-    ./logs/base-20240710.log
            std::ostream _ofs;
            size_t _max_fsize;//最大文件大小,当前文件超过了这个大小就要切换文件
            size_t _cur_fsize;//记录当前文件已经写入的数据大小
    };
    //简单工厂模式,进行生成管理
    class SinkFactory{
    };
}
#endif
  1. 把框架的功能以及具体实现编写完成。
/*日志落地模块的实现
1.抽象落地基类
2.派生子类(根据不同的落地方向进行派生)
3.使用工厂模式进行创建与表示分离
*/
#ifndef __M_SINK_H__
#define __M_SINK_H__
#include "util.hpp"
#include <fstream>
#include <sstream>
#include <memory>
#include <cassert>
#include <unistd.h>
namespace logslearn
{
    // 抽象落地基类
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink() {}
        virtual ~LogSink() {}
        // 纯虚函数,日志落地功能
        virtual void log(const char *data, size_t len) = 0;
    };
    // 落地方向:标准输出
    class StdoutSink : public LogSink
    {
    public:
        // 将日志消息写入到标准输出
        void log(const char *data, size_t len)
        {
            std::cout.write(data, len); // 因为日志输出不一定是字符串,所以不能直接打印,因此需要调用write接口,从data位置开始写,写入len长度的数据
        }
    };
    // 落地方向:指定文件
    class FileSink : public LogSink
    {
    public:
        // 构造时存入文件名,并打开文件,将操作句柄管理起来
        FileSink(const std::string &pathname) : _pathname(pathname)
        {
            // 1.创建日志文件所在的目录,没有文件就创建文件
            logsLearn::util::File::createDirectory(logsLearn::util::File::path(pathname));
            // 2.按特殊方式打开文件
            _ofs.open(_pathname, std::ios::binary | std::ios::app); // 二进制可写可追加权限
            assert(_ofs.is_open());
        }
        // 将日志消息写入到标准输出
        void log(const char *data, size_t len)
        {
            _ofs.write(data, len);
            assert(_ofs.good()); // 打开失败就报错
        }

    private:
        std::string _pathname;
        std::ofstream _ofs; // 会默认以写的方式打开文件
    };
    // 落地方向:滚动文件(以大小进行滚动)
    class RoolBySizeSink : public LogSink
    {
    public:
        // 构造时存入文件名,并打开文件,将操作句柄管理起来// 需要用户告知,基础的文件名和文件大小
        RoolBySizeSink(const std::string &basename, size_t max_fsize) :_basename(basename), _max_fsize(max_fsize), _cur_fsize(0),_name_count(0){
            std::string pathname=createNewFile();
            // 1.创建日志文件所在的目录,没有文件就创建文件
            logsLearn::util::File::createDirectory(logsLearn::util::File::path(pathname));
            // 2.按特殊方式打开文件
            _ofs.open(pathname, std::ios::binary | std::ios::app); //打开文件 二进制可写可追加权限
            assert(_ofs.is_open());
        } 
        // 将日志消息写入到标准输出--写入前判断文件大小,超过了最大大小就要切换文件
        void log(const char *data, size_t len){
            if(_cur_fsize>=_max_fsize){
                _ofs.close();//打开文件,就必须关闭文件(这里关闭以前的文件)
                std::string pathname =createNewFile();//创建新文件
                 _ofs.open(pathname, std::ios::binary | std::ios::app); //打开文件 二进制可写可追加权限
                 assert(_ofs.is_open());//打开失败就报错
                 _cur_fsize=0;
            }
            _ofs.write(data,len);
            assert(_ofs.good());//检测文件流状态和文件读写过程是否正常    
             _cur_fsize+=len;   
        }

    private:
        // 创建一个新文件,不需要用户去创建,所有我们把权限设置为私有
        std::string createNewFile(){
            //获取系统时间,以时间来构造文件名的扩展名
            time_t t=logsLearn::util::Data::now();
            struct tm lt;
            localtime_r(&t,&lt);
            std::stringstream filename;
            filename<<_basename;
            filename<<lt.tm_year+1900;
            filename<<lt.tm_mon+1;
            filename<<lt.tm_mday;
            filename<<lt.tm_hour;
            filename<<lt.tm_min;
            filename<<lt.tm_sec;
            filename<<"-";
            filename<<_name_count++;
            filename<<".log";
            return filename.str();
        }; // 进行大小判断,超过指定大小则需要创建新文件,将一个时间戳,转化为时间结构
    private:
        // 通过基础文件名+扩展文件名(以时间生成)组成一个实际的当前输出文件名
        size_t _name_count;//名称计数器
        std::string _basename; // 文件的基础名字如./logs/base-    ./logs/base-20240710.log
        std::ofstream _ofs;
        size_t _max_fsize; // 最大文件大小,当前文件超过了这个大小就要切换文件
        size_t _cur_fsize; // 记录当前文件已经写入的数据大小
    };
    // 简单工厂模式,进行生成管理
    class SinkFactory
    {
    };
}
#endif
  1. 使用简单工厂模式
    // 简单工厂模式,进行生成管理
    //SinkType通过模板参数,可以生产我们需要的落地方式,因为落地方式需要传参的参数不一样,这里我们需要用到不定参的知识
   
    class SinkFactory
    {
        public:
         template<typename SinkType,typename ...Args>
        static LogSink::ptr create(Args && ...args){
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }

    };
  1. 功能测试以及完善
//测试代码
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "format.hpp"
#include "sink.hpp"
int main()
{
    //日志落地模块的测试
    logslearn::LogMsg msg(logslearn::loglevel::value::INFO,53,"main.c","root","格式化功能测试...");
    logslearn::Formatter fmt("abc%%abc[%d{%H:%M:%S}]%m%n");
    std::string str=fmt.format(msg);
    //设置落地方向
    logslearn::LogSink::ptr stdout_lsp=logslearn::SinkFactory::create<logslearn::StdoutSink>();//标准输出落地
    logslearn::LogSink::ptr file_lsp=logslearn::SinkFactory::create<logslearn::FileSink>("./logfile/test.log");//文件落地方式
    logslearn::LogSink::ptr roll_lsp=logslearn::SinkFactory::create<logslearn::RoolBySizeSink>("./logfile/test.log",1024*1024);//滚动文件落地方式
    //通过指针去控制打印的日志
    stdout_lsp->log(str.c_str(),str.size());//把str转化成常量字符
    file_lsp->log(str.c_str(),str.size());
    size_t cursize=0;
    size_t count=0;
    //用滚动文件的方法希望生产10个文件
    while(cursize<1024*1024*10)
    {
        std::string tmp=std::to_string(count++)+str;//每个生产的日志都有信号
        roll_lsp->log(tmp.c_str(),tmp.size());
        cursize+=tmp.size();
    }
    return 0;   
}

测试结果:
在这里插入图片描述
文件落地的日志消息
在这里插入图片描述
滚动文件的日志消息
在这里插入图片描述

2.2 日志落地模块功能功能扩展

  1. 扩展一个以时间作为日志文件滚动切换类型的日志落地模块
/*扩展一个以时间作为日志文件滚动切换类型的日志落地模块
   1.以时间进行文件滚动,实际上是以时间段进行滚动
       实现思想:以当前系统时间,取模获得时间段大小,可以得到当前时间段是第几个时间段
           time(nullptr)%gap;
           每次以当前系统时间取模,判断与当前文件的时间段是否一致,不一致代表不是同一个时间段
   */
// 使用枚举来确定时间段的大小
enum class TimeGap
{
    GAP_SECOND,
    GAP_MINUTE,
    GAP_HOUR,
    GAP_DAY,
};

class RollByTimeSink : public logslearn::LogSink
{
public:
    // 构造时存入文件名,并打开文件,将操作句柄管理起来
    RollByTimeSink(const std::string &basename, TimeGap gap_type) : _basename(basename)
    {
        switch (gap_type)
        {
        case TimeGap::GAP_SECOND:
            _gap_size = 1;
            break; // 以秒为时间段
        case TimeGap::GAP_MINUTE:
            _gap_size = 60;
            break; // 以分钟为时间段
        case TimeGap::GAP_HOUR:
            _gap_size = 3600;
            break; // 以小时为时间段
        case TimeGap::GAP_DAY:
            _gap_size = 3600 * 24;
            break; // 以天为时间段
        }
        _cur_gap = _gap_size==1?logsLearn::util::Data::now():logsLearn::util::Data::now() / _gap_size; // 获取当前是第几个时间段
        // 创建文件
        std::string filename = createNewFile();
        // 1.创建日志文件所在的目录,没有文件就创建文件
        logsLearn::util::File::createDirectory(logsLearn::util::File::path(filename));
        // 2.按特殊方式打开文件
        _ofs.open(filename, std::ios::binary | std::ios::app); // 打开文件 二进制可写可追加权限
        assert(_ofs.is_open());
    }
    // 将日志消息写入到标准输出,判断当前时间是否是当前文件的时间段,不是就要切换文件。
    void log(const char *data, size_t len)
    {
        time_t cur = logsLearn::util::Data::now(); // 获取当前系统时间,时间戳
        if ((cur / _gap_size) != _cur_gap)//(每次写日志时判断当前的时间段与上次的时间段是否是一致得,一致的话就写入,不一致就创建新文件)
        {
            _ofs.close();                                          // 打开文件,就必须关闭文件(这里关闭以前的文件)
            std::string pathname = createNewFile();                // 创建新文件
            _cur_gap = _gap_size==1?logsLearn::util::Data::now():logsLearn::util::Data::now() / _gap_size; // 获取当前是第几个时间段
            _ofs.open(pathname, std::ios::binary | std::ios::app); // 打开文件 二进制可写可追加权限
            assert(_ofs.is_open());                                // 打开失败就报错
        }
        _ofs.write(data, len);
        assert(_ofs.good()); // 检测文件流状态和文件读写过程是否正常
    }

protected:
    // 创建一个新文件,不需要用户去创建,所有我们把权限设置为私有
    std::string createNewFile()
    {
        // 获取系统时间,以时间来构造文件名的扩展名
        time_t t = logsLearn::util::Data::now();
        struct tm lt;
        localtime_r(&t, &lt);
        std::stringstream filename;
        filename << _basename;
        filename << lt.tm_year + 1900;
        filename << lt.tm_mon + 1;
        filename << lt.tm_mday;
        filename << lt.tm_hour;
        filename << lt.tm_min;
        filename << lt.tm_sec;
        filename << ".log";
        return filename.str();
    }
private:
    std::string _basename; // 基本文件名
    std::ofstream _ofs;    // 会默认以写的方式打开文件
    size_t _cur_gap;       // 当前是第几个时间段
    size_t _gap_size;      // 时间段的大小
};
  1. 对扩展的功能进行测试
int main()
{
     // 日志落地扩展模块的测试
    logslearn::LogMsg msg(logslearn::loglevel::value::INFO, 53, "main.c", "root", "格式化功能测试...");
    logslearn::Formatter fmt("abc%%abc[%d{%H:%M:%S}]%m%n");
    std::string str = fmt.format(msg);
    // 设置落地方向
    logslearn::LogSink::ptr time_lsp = logslearn::SinkFactory::create<RollByTimeSink>("./logfile/rool-", TimeGap::GAP_MINUTE); // 滚动文件落地方式
    time_t old=logsLearn::util::Data::now();//获取当前系统时间
    while (logsLearn::util::Data::now()< old+63)//写3秒的数据
    {
        time_lsp->log(str.c_str(), str.size());
        usleep(1000);//等待1毫秒
    }
    return 0;
}
  1. 显示测试的结果
    创建了5个文件,每个文件有900条左右的日志。
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1925259.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows 子系统WSL2 Ubuntu使用事项

Windows 子系统WSL2 Ubuntu使用事项 要使外部设备能够访问运行在 Windows 上的 WSL2 实例&#xff0c;你可以端口转发的方法。由于 WSL2 是在虚拟化环境中运行&#xff0c;直接访问比 WSL1 更为复杂. 1 如何实现子系统可以被外部系统SSH 1.1 端口转发: 通过windows代理WSL2的…

微信视频号的视频怎么下载到本地?快速教你下载视频号视频

天来说说市面上常见的微信视频号视频下载工具&#xff0c;教大家快速下载视频号视频&#xff01; 方法一&#xff1a;缓存方法 该方法来源早期视频技术&#xff0c;因早期无法将大量视频通过网络存储&#xff0c;故而会有缓存视频文件到手机&#xff0c;其目的为了提高用户体验…

stm32入门-----初识stm32

目录 前言 ARM stm32 1.stm32家族 2.stm32的外设资源 3.命名规则 4.系统结构 5.引脚定义 6.启动配置 7.STM32F103C8T6芯片 8.STM32F103C8T6芯片原理图与最小系统电路 前言 已经很久没跟新了&#xff0c;上次发文的时候是好几个月之前了&#xff0c;现在我是想去学习st…

C++继承和多态

目录 继承 继承的意义 访问限定符、继承方式 赋值兼容规则&#xff08;切片&#xff09; 子类的默认成员函数 多继承 继承is a和组合has a 多态 什么是多态 形成多态的条件 函数重载&#xff0c;隐藏&#xff0c;重写的区别 override和final 多态原理 继承 继承的…

FinalShell介绍,安装与应用

目录 一、什么是finalshell 二、finalshell功能 三、为什么要用finalshell 四、安装finalshell 五、finalshell使用 1.添加连接 获取虚拟ip地址 2.启动连接 一、什么是finalshell FinalShell是一体化的的服务器,网络管理软件,不仅是ssh客户端,还是功能强大的开发,运维工…

在RHEL9.4上启用SFTP服务

FTP存在的不足&#xff1a; 明文传输 FTP传输的数据&#xff08;包括用户名、密码和文件内容&#xff09;都是明文的&#xff0c;这意味着数据可以被网络上的任何人截获并读取。没有内置的加密机制&#xff0c;容易受到中间人攻击。 被动模式下的端口问题 FTP的被动模式需要…

server nat表和会话表的作用及NAT地址转换详细

本章节主要讲nat技术的基础 -会话表的建立也是看5元组 -状态检测技术的回包一样也看5元组&#xff0c;但是状态检测技术会看的除开5元组还有更多东西 老哥&#xff0c;你真的应该好好注意一个东西&#xff1a;我们的会话表只是为了后续包的转发&#xff0c;会话表是记录的首…

C++:哈希表

哈希表概念 哈希表可以简单理解为&#xff1a;把数据转化为数组的下标&#xff0c;然后用数组的下标对应的值来表示这个数据。如果我们想要搜索这个数据&#xff0c;直接计算出这个数据的下标&#xff0c;然后就可以直接访问数组对应的位置&#xff0c;所以可以用O(1)的复杂度…

澳门建筑插画:成都亚恒丰创教育科技有限公司

澳门建筑插画&#xff1a;绘就东方之珠的斑斓画卷 在浩瀚的中华大地上&#xff0c;澳门以其独特的地理位置和丰富的历史文化&#xff0c;如同一颗璀璨的明珠镶嵌在南国海疆。这座城市&#xff0c;不仅是东西方文化交融的典范&#xff0c;更是建筑艺术的宝库。当画笔轻触纸面&a…

能源园区可视化管理系统

利用图扑 HT 可视化打造能源园区管理系统&#xff0c;实时监控和优化能源分配&#xff0c;提升园区运行效率&#xff0c;增强安全管理&#xff0c;推动绿色和可持续发展。

信立方大模型 | 以AI之钥,开拓智能守护新疆界

在当前网络安全形势日益复杂的背景下&#xff0c;技术的进步不仅带来了便利&#xff0c;也使得网络攻击手段更加多样化和隐蔽化。据悉&#xff0c;国外某研究团队已成功利用GPT技术开发出一种黑客智能体框架&#xff0c;该框架能够深入研读CVE&#xff08;通用漏洞披露&#xf…

MATLAB激光通信和-积消息传递算法(Python图形模型算法)模拟调制

&#x1f3af;要点 &#x1f3af;概率论和图论数学形式和图结构 | &#x1f3af;数学形式、图结构和代码验证贝叶斯分类器算法&#xff1a;&#x1f58a;多类型&#xff1a;朴素贝叶斯&#xff0c;求和朴素贝叶斯、高斯朴素贝叶斯、树增强贝叶斯、贝叶斯网络增强贝叶斯和半朴素…

Android12 MultiMedia框架之GenericSource extractor

前面两节学习到了各种Source的创建和extractor service的启动&#xff0c;本节将以本地播放为例记录下GenericSource是如何创建一个extractor的。extractor是在PrepareAsync()方法中被创建出来的&#xff0c;为了不过多赘述&#xff0c;我们直接从GenericSource的onPrepareAsyn…

LeetCode刷题笔记第3011题:判断一个数组是否可以变为有序

LeetCode刷题笔记第3011题&#xff1a;判断一个数组是否可以变为有序 题目&#xff1a; 想法&#xff1a; 使用冒泡排序进行排序&#xff0c;在判断大小条件时加入判断二进制下数位为1的数目是否相同&#xff0c;相同则可以进行互换。最后遍历数组&#xff0c;相邻两两之间是…

17集 如何用ESP-IDF编译ESP-DL深度学习工程-《MCU嵌入式AI开发笔记》

17集 如何用ESP-IDF编译ESP-DL深度学习工程-《MCU嵌入式AI开发笔记》 参考文档&#xff1a;ESP-DL 用户指南&#xff1a; https://docs.espressif.com/projects/esp-dl/zh_CN/latest/esp32/index.html 和https://docs.espressif.com/projects/esp-dl/zh_CN/latest/esp32/get-s…

Qt Mqtt客户端 + Emqx

环境 Qt 5.14.2 qtmqtt mqttx 功能 QT Mqtt客户端 qtmqtt 下载 qtmqtt (注意下载与QT版本相符的库)并使用QT 编译 编译完成后需要的文件: emqx 1.虚拟机中安装emqx,并启动 curl -s https://assets.emqx.com/scripts/install-emqx-deb.sh | sudo bash sudo apt-get inst…

Java实现数据结构——双链表

目录 一、前言 二、实现 2.1 类的创建 三、对链表操作实现 3.1 打印链表 3.2 插入数据 3.2.1 申请新节点 3.2.2 头插 ​编辑 3.2.3 尾插 3.2.4 链表长度 3.2.5 任意位置插入 3.3 删除数据 3.3.1 头删 3.3.2 尾删 3.3.3 删除指定位置数据 3.3.4 删除指定数据 3…

王道计算机考研数据结构思维导图笔记(持续更新)

第1章 绪论 1.1 数据结构的基本概念 1.1.1 基本概念和术语 1.1.1 数据结构三要素 1.2 算法和算法评价 1.2.1算法的基本概念 1.2.2 算法效率的度量 第2章 线性表 2.1 线性表的定义和基本操作 2.1.1 线性表的定义 2.1.2 线性表的基本操作 2.2.1 顺序表上的定义 2.2.2 顺序…

Power Apps使用oData访问表数据并赋值前端

在使用OData查询语法通过Xrm.WebApi.retrieveMultipleRecords方法过滤数据时&#xff0c;你可以指定一个OData $filter 参数来限制返回的记录集。 以下是一个使用Xrm.WebApi.retrieveMultipleRecords方法成功的例子&#xff0c;它使用了OData $filter 参数来查询实体的记录&am…

期货交易记录20240714

文章目录 期货交易系统构建步骤一、选品二、心态历练三、何时开仓3.1、开仓纪律3.2、开仓时机3.3、开仓小技巧 四、持仓纪律五、接下来的计划 2024年7月15号&#xff0c;期货交易第6篇记录。这一篇文中主要记录下&#xff0c;根据交易保证金筛选品种。 交易记录&#xff1a;目…