[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条左右的日志。
    在这里插入图片描述

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

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

相关文章

深度学习工具和资源推荐:全面指南

今天我们来聊聊深度学习的工具和资源。要学好深度学习&#xff0c;除了理论知识&#xff0c;还需要掌握一些强大的工具和找到好的资源。以下是我在学习过程中发现的一些非常有用的工具和资源&#xff0c;希望对你们有帮助。 目录 工具推荐 1. Python编程语言 2. TensorFlow…

接口测试返回参数的自动化对比!

引言 在现代软件开发过程中&#xff0c;接口测试是验证系统功能正确性和稳定性的核心环节。接口返回参数的对比不仅是确保接口功能实现的手段&#xff0c;也是测试过程中常见且重要的任务。为了提高对比的效率和准确性&#xff0c;我们可以通过自动化手段实现这一过程。本文将…

WGCLOUD登录页面支持输入验证码吗

支持的 v3.5.3版本开始&#xff0c;WGCLOUD支持在登录页面配置输入验证码&#xff0c;我们可以根据自己的场景需要&#xff0c;配置是否在登录页面显示验证码&#xff0c;如下说明 登录页面添加验证码说明 - WGCLOUD

[超级详细系列]ubuntu22.04配置深度学习环境(显卡驱动+CUDA+cuDNN+Pytorch)--[3]安装cuDNN与Pytorch

本次配置过程的三篇博文分享分别为为&#xff1a; [超级详细系列]ubuntu22.04配置深度学习环境(显卡驱动CUDAcuDNNPytorch)--[1]安装显卡驱动 [超级详细系列]ubuntu22.04配置深度学习环境(显卡驱动CUDAcuDNNPytorch)--[2]安装Anaconda与CUDA [超级详细系列]ubuntu22.04配置深…

代码随想录 day38 动态规划part04

416. 分割等和子集 给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集&#xff0c;使得两个子集的元素和相等。注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200示例 1:输入: [1, 5, 11, 5] 输出: true 解释: 数组可以分割成 [1, 5, 5] 和 [11]. …

Web学习day04

mybatis 目录 mybatis 文章目录 一、查询 1.1结果映射 1.2多条件查询 1.3模糊查询 二、XML 书写规范 三、动态SQL 四、配置文件 4.1settings标签 4.2mappers标签 4.3environments标签 五、案例 5.1数据表 5.2实现类 5.3mapper实现 5.4工具类实现 5.5XML动态…

解决spring boot中使用拦截器导致swagger文档无法访问

目录 问题场景 解决方案 问题场景 我们的spring boot项目通常会使用接口文档管理依赖如knife4j(swagger3) Spring Boot3整合knife4j(swagger3)_springboot3 knife4j-CSDN博客 通常也会使用拦截器来做登录鉴权、接口限流等操作&#xff0c;但是使用拦截器会导致swagger接口…

FFmpeg学习(五)-- libswresample使用说明及函数介绍

libswresample Audio合成和重采样 libswresample库用来进行audio数据的合成和重采样操作。调用流程&#xff1a; 调用 swr_alloc 创建SwrContext结构体。设置SwrContext参数&#xff0c;有两种方法&#xff1a; 调用av_opt_set_xx函数逐项设置参数&#xff1b;swr_alloc_set_…

Python应用 | 基于flask-restful+AntDesignVue实现的一套图书管理系统

本文将分享个人自主开发的一套图书管理系统&#xff0c;后端基于Python语言&#xff0c;采用flask-restful开发后端接口&#xff0c;前端采用VueAntDesignVue实现。对其他类似系统的实现&#xff0c;比如学生管理系统等也有一定的参考作用。有问题欢迎留言讨论~ 关注公众号&am…

04.为什么line-height是无单位的 兄弟元素淡出效果 蚀刻文字效果

为什么 CSS 中的 line-height 应该是无单位的? 我经常听说 line-height 应该始终是无单位的。在我早期的编码年代,我没有过多地质疑这一点,但最近我开始想知道为什么会这样。在我看来,1.5 和 1.5em 应该产生相同的结果,对吧?事实证明,它们并非如此。 两者之间有一个细微的区…

记一下blender曲线阵列

先说一下如何正常使用这个 这一次我是用来贴瓷砖 随便创建一个mesh 然后添加一个阵列修改器&#xff0c;然后再给他添加一个curve修改器&#xff0c;使用constant offset去偏移他 这里有个小细节 我第一次创建的curve 我选取之后&#xff0c;死活无法沿着曲线阵列&#xff…

Dockerfile指令

Dockerfile指令 1、Dockerfile指令? 1)、COPY&#xff1a;类似ADD&#xff0c;将我们的文件拷贝到镜像中&#xff0c;也就是复制本地(宿主机)上的文件到镜像。 COPY [--chown<user>:<group>] ["<源路径1>",... "<目标路径>"] …

.快速幂.

按位与&#xff08;Bitwise AND&#xff09;是一种二进制运算&#xff0c;它逐位对两个数的二进制表示进行运算。对于每一位&#xff0c;只有两个相应的位都为1时&#xff0c;结果位才为1&#xff1b;否则&#xff0c;结果位为0。如&#xff1a;十进制9 & 5转化为二进制&am…

ActiveMQ-CVE-2023-46604

Apache ActiveMQ OpenWire 协议反序列化命令执行漏洞 OpenWire协议在ActiveMQ中被用于多语言客户端与服务端通信。在Apache ActvieMQ5.18.2版本以及以前&#xff0c;OpenWire协议通信过程中存在一处反序列化漏洞&#xff0c;该漏洞可以允许具有网络访问权限的远程攻击者通过操作…

opencv 中如何通过欧式距离估算实际距离(厘米)

1&#xff1a;这个方法个人测试觉得是正确的&#xff0c;误差较小&#xff0c;目前满足我当前的需求&#xff0c;如果方法不对&#xff0c;请大家评论&#xff0c;完善。 2&#xff1a;确保拍摄的参照物是垂直的&#xff0c;如果不垂直&#xff0c;就会有误差&#xff0c;不垂…

Django是干什么的?好用么?

Django是一个开源的Python Web框架&#xff0c;用于快速开发高质量的Web应用程序。它提供了许多功能和工具&#xff0c;以简化常见的Web开发任务&#xff0c;如路由、请求处理、数据库管理等。 Django的优点包括&#xff1a; 简单易用&#xff1a;Django提供了清晰的文档和丰…

HASHTABLE, HASHMAP,TreeMap区别

Hashtable、HashMap 和 TreeMap 都是Java集合框架中的实现&#xff0c;它们提供了键值对映射的数据结构&#xff0c;但它们在实现细节、性能特性和使用场景上有所不同&#xff1a; 1. Hashtable: - Hashtable 是遗留下来的类&#xff0c;继承自 Dictionary 类。 - 它实现…

低代码商城构建专家:Mall-Cook

Mall-Cook&#xff1a;用Mall-Cook&#xff0c;让电商创新触手可及- 精选真开源&#xff0c;释放新价值。 概览 Mall-Cook是一个面向未来的商城低代码开发平台&#xff0c;它通过提供直观的可视化界面&#xff0c;让开发者和商家能够快速构建和部署跨平台的电商解决方案。这个…

微信小程序如何实现登陆和注册功能?

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

SQL基本查询

简单查询 单表查询 查询表的全部信息&#xff1a; --student是要查询的表 select * from student 使用别名查询表中的某个字段&#xff0c;或使用别名创建一个新的字段&#xff1a; select name as studentname from student --把查询出来的名字的列名改为学生姓名 …