当前位置: 首页 > news >正文

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

同步异步日志系统

  • 一、日志消息格式化设计
    • 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;}};//线程IDclass 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;}};// 线程IDclass 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.logstd::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.logstd::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条左右的日志。
    在这里插入图片描述

相关文章:

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

同步异步日志系统 一、日志消息格式化设计1.1 格式化子项类的定义和实现1.2 格式化类的定义和实现 二、日志落地类设计2.1 日志落地模块功能实现与测试2.2 日志落地模块功能功能扩展 一、日志消息格式化设计 日志格式化模块的作用&#xff1a;对日志消息进行格式化&#xff0c…...

Qt项目:基于Qt实现的网络聊天室---TCP服务器和token验证

文章目录 TCP服务器设计客户端TCP管理者ChatServerAsioIOServicePoolSession层LogicSystem总结 token验证模块完善protoStatusServer验证token客户端处理登陆回包用户管理登陆界面 本篇完成的模块是TCP服务器的设计和token验证 TCP服务器设计 客户端TCP管理者 因为聊天服务要…...

深入理解C++构造函数

目录 1.引言 2.默认构造函数 3.自定义构造函数 4.带继承关系类的构造函数 5.带多重继承关系类的构造函数 6.带虚继承关系类的构造函数 7.总结 1.引言 对于学过C的来说&#xff0c;构造函数是非常熟悉不过的了。但是你真正了解它吗&#xff1f;构造函数内部初始化变量的顺…...

J025_斗地主游戏案例开发(简版)

一、需求描述 完成斗地主游戏的案例开发。 业务&#xff1a;总共有54张牌&#xff1b; 点数&#xff1a;3、4、5、6、7、8、9、10、J、Q、K、A、2 花色&#xff1a;黑桃、红桃、方片、梅花 大小王&#xff1a;大王、小王 点数分别要组合4种花色&#xff0c;大小王各一张。…...

路径规划 | 飞蛾扑火算法求解二维栅格路径规划(Matlab)

目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 路径规划 | 飞蛾扑火算法求解二维栅格路径规划&#xff08;Matlab&#xff09;。 飞蛾扑火算法&#xff08;Firefly Algorithm&#xff09;是一种基于自然界萤火虫行为的优化算法&#xff0c;在路径规划问题中也可以应…...

优化Cocos Creator 包体体积

优化Cocos Creator 包体体积 引言一、优化图片文件体积&#xff1a;二、优化声音文件体积&#xff1a;三、优化引擎代码体积&#xff1a;四、 优化字体字库文件大小&#xff1a; 引言 优化Cocos Creator项目的包体体积是一个常见且重要的任务&#xff0c;尤其是在移动设备和网…...

TCPDump协议分析工具

TCPDump协议分析工具 TCPDump是一个强大的命令行工具&#xff0c;用于捕获和分析网络数据包。它能够实时监控和记录网络流量&#xff0c;帮助网络管理员和安全专家排查网络问题、分析流量和检测网络攻击。以下是TCPDump的详细介绍&#xff0c;包括其安装、基本使用、过滤规则和…...

土壤分析仪:解密土壤之奥秘的科技先锋

在农业生产和生态保护的道路上&#xff0c;土壤的质量与状况一直是我们关注的焦点。土壤分析仪&#xff0c;作为现代科技在农业和环保领域的杰出代表&#xff0c;以其高效、精准的分析能力&#xff0c;为我们揭示了土壤的奥秘&#xff0c;为农业生产提供了科学指导&#xff0c;…...

计算1的数量

1. 计算1的数量 题目ID&#xff1a;9809必做题100分 最新提交&#xff1a; Accepted 100 分 历史最高&#xff1a; Accepted 100 分 时间限制: 1000ms 空间限制: 524288kB 题目描述 给定一个n*m的二进制矩阵&#xff0c;请你数一数矩阵中完全被0上下左右包围的1的数…...

Linux udp编程

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…...

【开源项目】Rust开发复制文件夹目录结构工具

说明 由于我经常需要在多个大容量的移动硬盘中查找和新增文件&#xff0c;我希望把硬盘的目录结构放到服务器的自建网盘中&#xff0c;因此开发了这个工具&#xff0c;使得在不同硬盘之间的文件管理变得更加便捷 项目地址&#xff1a;https://github.com/VinciYan/folder_clon…...

PostgreSQL的pg_dirtyread工具

PostgreSQL的pg_dirtyread工具 pg_dirtyread 是一个第三方PostgreSQL扩展&#xff0c;它允许用户读取数据库文件中的“脏”数据&#xff0c;即那些被标记为删除或不再可见的数据。这个扩展对于数据恢复和调试非常有用&#xff0c;尤其是在需要恢复被删除或更新前的数据时。 以…...

苹果梦碎:Vision Pro的辉煌与失落,苹果已决定暂停 Vision Pro 后续产品的研发工作

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 苹果Vision Pro&#xff1a;科技巨头的跌宕起伏 在科技的海洋中&#xff0c;苹果公司一直以其创新精神和卓越品质引领潮流。然而&#xff0c;即…...

推荐一款uniapp拖动验证码插件

插件地址&#xff1a;易盾验证码 - DCloud 插件市场 具体使用方式访问插件地址自行获取...

十年期国债收益率

十年期国债收益率是指政府发行的、期限为十年的国债的年化收益率。它被广泛视为一个国家经济健康状况和未来经济前景的重要指标&#xff0c;同时也是金融市场中的一个重要基准利率。 下面我将详细解释十年期国债收益率的相关内容及其意义。 十年期国债收益率的意义 经济健康的…...

使用Go编写的持续下行测速脚本,快速消耗流量且不伤硬盘

GoSpeed 声明 此工具仅用于测试与学习,请勿用于非法用途,如使用此程序请确保所有下载的内容都拥有合法的使用权或分发权&#xff0c;避免侵犯版权、恶意访问 此工具仅用于测试与学习,请勿用于非法用途,如使用此程序请确保所有下载的内容都拥有合法的使用权或分发权&#xff0c…...

保护国外使用代理IP的安全方法

为了保护在国外使用代理IP的安全&#xff0c;用户可以采取以下方法&#xff1a; 1. 选择可信的代理服务器 在选择代理服务器时&#xff0c;用户应该选择那些经过验证和信任的服务器&#xff0c;如知名的VPN服务提供商。这些服务器通常具有更高的安全性和隐私保护措施。 2. 使用…...

18集 学习ESP32的ESP-DL深度学习教程-《MCU嵌入式AI开发笔记》

18集 学习ESP32的ESP-DL深度学习教程-《MCU嵌入式AI开发笔记》 参考文档&#xff1a;https://docs.espressif.com/projects/esp-dl/zh_CN/latest/esp32/tutorials/index.html 使用TVM自动生成模型部署项目 本案例介绍了使用 TVM 部署模型的完整流程。 该项目基于 TVM v0.14…...

jmeter-beanshell学习9-放弃beanshell

写这篇时候道心不稳了&#xff0c;前面写了好几篇benashell元件&#xff0c;突然发现应该放弃。想回去改前面的文章&#xff0c;看了看无从下手&#xff0c;反正已经这样了&#xff0c;我淋了雨&#xff0c;那就希望别人也没有伞吧&#xff0c;哈哈哈哈&#xff0c;放在第九篇送…...

Web 性能入门指南-1.5 创建 Web 性能优化文化的最佳实践

最成功的网站都有什么共同点&#xff1f;那就是他们都有很强的网站性能和可用性文化。以下是一些经过验证的有效技巧和最佳实践&#xff0c;可帮助您建立健康、快乐、值得庆祝的性能文化。 创建强大的性能优化文化意味着在你的公司或团队中创建一个如下所示的反馈循环&#xff…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

均衡后的SNRSINR

本文主要摘自参考文献中的前两篇&#xff0c;相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程&#xff0c;其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt​ 根发送天线&#xff0c; n r n_r nr​ 根接收天线的 MIMO 系…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解

在 C/C 编程的编译和链接过程中&#xff0c;附加包含目录、附加库目录和附加依赖项是三个至关重要的设置&#xff0c;它们相互配合&#xff0c;确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中&#xff0c;这些概念容易让人混淆&#xff0c;但深入理解它们的作用和联…...

第7篇:中间件全链路监控与 SQL 性能分析实践

7.1 章节导读 在构建数据库中间件的过程中&#xff0c;可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中&#xff0c;必须做到&#xff1a; &#x1f50d; 追踪每一条 SQL 的生命周期&#xff08;从入口到数据库执行&#xff09;&#…...

自然语言处理——文本分类

文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益&#xff08;IG&#xff09; 分类器设计贝叶斯理论&#xff1a;线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别&#xff0c; 有单标签多类别文本分类和多…...