网站友情链接与排名,网站从建设到上线流程,站长工具seo综合查询工具,网站降权了怎么办C基于多设计模式下的同步异步日志系统day3 #x1f4df;作者主页#xff1a;慢热的陕西人 #x1f334;专栏链接#xff1a;C基于多设计模式下的同步异步日志系统 #x1f4e3;欢迎各位大佬#x1f44d;点赞#x1f525;关注#x1f693;收藏#xff0c;基于多设计模式下的同步异步日志系统day3 作者主页慢热的陕西人 专栏链接C基于多设计模式下的同步异步日志系统 欢迎各位大佬点赞关注收藏留言 主要内容日志项目的输出格式化类的设计和日志落地(LogSink)类设计(简单工厂模式) 文章目录 C基于多设计模式下的同步异步日志系统day31.代码设计1.1日志输出格式化类设计1.2日志落地(LogSink)类设计(简单工厂模式) 1.代码设计
1.1日志输出格式化类设计
⽇志格式化Formatter类主要负责格式化⽇志消息。其主要包含以下内容 pattern成员保存⽇志输出的格式字符串。 ◦ %d⽇期 ◦ %T缩进 ◦ %t线程id ◦ %p⽇志级别 ◦ %c⽇志器名称 ◦ %f⽂件名 ◦ %l⾏号 ◦ %m⽇志消息 ◦ %n换⾏ std::vectorFormatItem::ptritems成员⽤于按序保存格式化字符串对应的⼦格式化对象
FormatItem类主要负责⽇志消息⼦项的获取及格式化。其包含以下⼦类
MsgFormatItem表⽰要从LogMsg中取出有效⽇志数据LevelFormatItem表⽰要从LogMsg中取出⽇志等级NameFormatItem表⽰要从LogMsg中取出⽇志器名称ThreadFormatItem表⽰要从LogMsg中取出线程IDTimeFormatItem表⽰要从LogMsg中取出时间戳并按照指定格式进⾏格式化 CFileFormatItem表⽰要从LogMsg中取出源码所在⽂件名CLineFormatItem表⽰要从LogMsg中取出源码所在⾏号TabFormatItem表⽰⼀个制表符缩进NLineFormatItem表⽰⼀个换⾏OtherFormatItem表⽰⾮格式化的原始字符串
⽰例“[%d{%H:%M:%S}]%m%n”
pattern [%d{%H:%M:%S}] %m%n
items {
{OtherFormatItem(), [},
{TimeFormatItem(), %H:%M:%S},
{OtherFormatItem(), ]},
{MsgFormatItem (), },
{NLineFormatItem (), }
}
LogMsg msg {
size_t _line 22;
size_t _ctime 12345678;
std::thread::id _tid 0x12345678;
std::string _name logger;
std::string _file main.cpp;
std::string _payload 创建套接字失败;
LogLevel::value _level ERROR;
};格式化的过程其实就是按次序从Msg中取出需要的数据进⾏字符串的连接的过程。 最终组织出来的格式化消息“[22:32:54]创建套接字失败\n”
代码实现
#ifndef __M_FMT_H__
#define __M_FMT_H__#includemessage.hpp
#includetime.h
#includevector
#includecassert
#includesstreamnamespace xupt
{//抽象格式化子类基类class FormatItem{public:using ptr std::shared_ptrFormatItem;virtual void format(std::ostream out, const LogMsg msg) 0; };//派生格式化子类项---消息等级时间文件名行号线程ID日志器名制表符换行其他class MsgFormatItem : public FormatItem{public:virtual void format(std:: ostream out, const LogMsg msg){out msg._payload;}};class LevelFormatItem : public FormatItem{public:virtual void format(std:: ostream out, const LogMsg msg){out LogLevel::toString(msg._level);}};class TimeFormatItem : public FormatItem{public:TimeFormatItem(const std::string fmt %H:%M:%S):_time_fmt(fmt){}virtual void format(std:: ostream out, const LogMsg msg){struct tm t;localtime_r(msg._ctime, t);char tmp[128];strftime(tmp, 127, _time_fmt.c_str(), t);out tmp;}private:std::string _time_fmt;}; class FileFormatItem : public FormatItem{public:virtual void format(std:: ostream out, const LogMsg msg){out msg._file;}}; class LineFormatItem : public FormatItem{public:virtual void format(std:: ostream out, const LogMsg msg){out msg._line;}}; class ThreadFormatItem : public FormatItem{public:virtual void format(std:: ostream out, const LogMsg msg){out msg._tid;}}; class LoggerFormatItem : public FormatItem{public:virtual void format(std:: ostream out, const LogMsg msg){out msg._logger;}}; class TabFormatItem : public FormatItem{public:virtual void format(std:: ostream out, const LogMsg msg){out \t;}}; class NewLineFormatItem : public FormatItem{public:virtual void format(std:: ostream out, const LogMsg msg){out \n;}}; class OtherFormatItem : public FormatItem{public:OtherFormatItem(const std::string str):_str(str){}virtual void format(std:: ostream out, const LogMsg msg){out _str;}private:std::string _str;}; class Formatter{public:Formatter(const std::string pattern [%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n):_pattern(pattern){assert(parsePattern()); //如果解析失败直接跳断言}//对msg进行格式化void format(std::ostream out ,const LogMsg Msg){for(auto item : _items){item-format(out, Msg);}}std::string format(const LogMsg Msg){std::stringstream ss;format(ss, Msg);return ss.str();}private://对格式化规则字符串进行解析bool parsePattern(){//1.对格式化规则字符串进行解析//abcd[%d{%H:%M:%S}][%p]%T%m%nstd::vectorstd::pairstd::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;}//%的情况//1.双%的情况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();}//2.%后是格式化字符的情况pos 1;//这一步之后pos指向格式化字符位置if(pos _pattern.size()){std::cout %后没有对应的格式化字符 std::endl;return false;}key _pattern[pos];//2.{}的情况pos 1;if(_pattern[pos] {){pos 1; //这时候pos指向子规则的起始位置while(pos _pattern.size() _pattern[pos] ! }){val.push_back(_pattern[pos]);}//走到了末尾跳出了循环则代表没有遇到}代表格式是错误的if(pos _pattern.size()) //没有找到}{std::cout 子规则{}匹配失败 std::endl;return false;}pos 1;}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_sharedTimeFormatItem(val);if(key t) return std::make_sharedThreadFormatItem();if(key c) return std::make_sharedLoggerFormatItem();if(key f) return std::make_sharedFileFormatItem();if(key l) return std::make_sharedLineFormatItem();if(key p) return std::make_sharedLevelFormatItem();if(key T) return std::make_sharedTabFormatItem();if(key m) return std::make_sharedMsgFormatItem();if(key n) return std::make_sharedNewLineFormatItem();if(key.empty()) return std::make_sharedOtherFormatItem(val);std::cout 没有对应的格式化字符串% key std::endl;abort();return FormatItem::ptr();}private:std::string _pattern; //格式化规则字符串std::vectorFormatItem::ptr _items; };}#endif1.2日志落地(LogSink)类设计(简单工厂模式)
⽇志落地类主要负责落地⽇志消息到⽬的地
它主要包括以下内容
Formatter⽇志格式化器主要是负责格式化⽇志消息。mutex互斥锁保证多线程⽇志落地过程中的线程安全避免出现交叉输出的情况。这个类⽀持可扩展其成员函数log设置为纯虚函数当我们需要增加⼀个log输出⽬标可以增加⼀个类继承⾃该类并重写log⽅法实现具体的落地⽇志逻辑。
目前实现了三个不同方向上的日志落地 标准输出StdoutSink 固定文件FileSink 滚动文件RollSinkBySize ◦ 滚动⽇志⽂件输出的必要性 • 由于机器磁盘空间有限我们不可能⼀直⽆限地向⼀个⽂件中增加数据 • 如果⼀个⽇志⽂件体积太⼤⼀⽅⾯是不好打开另⼀⽅⾯是即时打开了由于包含数据巨⼤也不利于查找我们需要的信息 • 所以实际开发中会对单个⽇志⽂件的⼤⼩也会做⼀些控制即当⼤⼩超过某个⼤⼩时如1GB我们就重新创建⼀个新的⽇志⽂件来滚动写⽇志。对于那些过期的⽇志⼤部分企业内部都有专⻔的运维⼈员去定时清理过期的⽇志或者设置系统定时任务定时清理过期⽇志 ◦ ⽇志⽂件的滚动思想 ⽇志⽂件滚动的条件有两个:⽂件⼤⼩和时间。我们可以选择 ⽇志⽂件在⼤于1GB的时候会更换新的⽂件每天定点滚动⼀个⽇志⽂件 本项⽬基于⽂件⼤⼩的判断滚动⽣成新的⽂件 /* 日志落地模块的实现1.抽象落地基类2.派生落地子类根据不同的落地方向3.使用工厂模式进行使用和生产的分离
*/#ifndef __M_SIK_H__
#define __M_SIK_H__#includeutil.hpp
#includememory
#includefstream
#includecassert
#includesstreamnamespace xupt
{class LogSink{public:using ptr std::shared_ptrLogSink;LogSink() {}virtual ~LogSink() {}virtual void log(const char* data, size_t len) 0; }; //落地方向标准输出class StdoutSink : public LogSink{public:virtual void log(const char* data, size_t len){std::cout.write(data, len);}};//落地方向指定文件class FileSink : public LogSink{public:FileSink(const std::string pathname):_pathname(pathname){//1.创建日志文件所在的目录util::File::CreateDirectory(util::File::path(_pathname));//2.创建并打开日志文件_ofs.open(_pathname, std::ios::binary | std::ios::app); //以二进制追加的方式写入//断言以下是否打开成功assert(_ofs.is_open());}//写入到文件virtual void log(const char* data, size_t len){_ofs.write(data, len);assert(_ofs.good());}private:std::string _pathname;std::ofstream _ofs;};//落地方向滚动文件以大小进行滚动class RollSinkBySize : public LogSink{public://构造函数传入文件名文件大小并管理文件输出句柄RollSinkBySize(const std::string basename, size_t max_size):_basename(basename), _max_size(max_size),_cur_size(0),_name_count(0){std::string pathname createNewFile();//1.创建日志文件所在的目录 util::File::CreateDirectory(util::File::path(pathname));//2.创建并打开日志文件_ofs.open(pathname, std::ios::binary | std::ios::app); //以二进制追加的方式写入//断言以下是否打开成功assert(_ofs.is_open()); }//将日志消息写入到文件写入前判断文件大小超过了最大大小就要切换文件virtual void log(const char* data, size_t len){if(_cur_size _max_size){//先关闭之前的文件_ofs.close();//创建新的文件std::string pathname createNewFile();_ofs.open(pathname,std::ios::binary | std::ios::app);//断言以下是否打开成功assert(_ofs.is_open()); //置零当前文件大小_cur_size 0;}//写入没有更改_cur_size_ofs.write(data, len);//断言以下是否成功写入assert(_ofs.good()); _cur_size len; }private://当文件大小达到限制的时候用于切换文件使用创建新的文件名并不能创建文件 std::string createNewFile(){//获取系统时间time_t t util::Date::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; //存储日志信息的文件, 命名规则我们一般是加一个固定的头尾部用时间来区分std::ofstream _ofs; //维护一个我们的文件输出句柄size_t _max_size; //单个文件的最大大小size_t _cur_size; //当前文件的大小算是一个优化这样我们就不用去频繁查看文件属性从而降低效率};/*拓展一个以时间作为文件滚动切换类型的日志落地模块
*/ class RollSinkByTime : public LogSink{public:enum class TimeGap{GAP_SECOND,GAP_MINUTE,GAP_HOUR,GAP_DAY,};public:RollSinkByTime(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 ? util::Date::now() : util::Date::now() % _gap_size; //初始化当前的时间片util::File::CreateDirectory(util::File::path(_basename)); //1.创建日志文件所在的目录 std::string filename createNewFile(); //2.创建文件名并且打开文件_ofs.open(filename,std::ios::binary | std::ios::app);assert(_ofs.is_open()); }//写入到文件,判断当前的时间段是否是当前文件的时间段不是则切换文件virtual void log(const char* data, size_t len){//获取系统时间time_t cur util::Date::now(); if(cur % _gap_size ! _cur_gap){_ofs.close();std::string filename createNewFile(); _ofs.open(filename,std::ios::binary | std::ios::app);assert(_ofs.is_open()); }//写入文件_ofs.write(data, len);assert(_ofs.good());}private://当时间片达到限制的时候用于切换文件使用,创建新的文件名并不能创建文件 std::string createNewFile(){//获取系统时间time_t t util::Date::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 .log;return filename.str();}private:std::string _basename;std::ofstream _ofs;size_t _cur_gap;size_t _gap_size;};class SinkFactory{public:templatetypename SinkType, typename ...Argsstatic LogSink::ptr create(Args ...args) //右值引用保持原来类型的属性用于后来的完美转发{return std::make_sharedSinkType(std::forwardArgs(args)...);}};
}#endif 到这本篇博客的内容就到此结束了。 如果觉得本篇博客内容对你有所帮助的话可以点赞收藏顺便关注一下 如果文章内容有错误欢迎在评论区指正