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

做企业培训的网站微网站建设包括哪些方面

做企业培训的网站,微网站建设包括哪些方面,超人气网站是这样建成的,建设银行网站适用浏览器目录 项目系统开发环境核心技术日志系统介绍为什么需要日志系统? 日志系统框架设计日志系统模块划分代码实现通用工具实现日志等级模块实现日志消息模块实现格式化模块实现落地模块实现日志器模块同步日志器异步日志器缓冲区实现异步工作器实现 回归异步日志器模块建造者模式日… 目录 项目系统开发环境核心技术日志系统介绍为什么需要日志系统? 日志系统框架设计日志系统模块划分代码实现通用工具实现日志等级模块实现日志消息模块实现格式化模块实现落地模块实现日志器模块同步日志器异步日志器缓冲区实现异步工作器实现 回归异步日志器模块建造者模式日志器管理模块实现 日志器模块具体实现整合模块总的头文件 日志系统使用样例: 项目系统 本项目主要实现一个日志系统其核心功能是:将一条日志消息按照指定格式和输出等级写入到指定位置; 本日志系统主要支持一下几个功能: 支持多级别日志消息的输出,比如:现在日志消息有A、B、C、D这几个等级如果我们想让B等级以上的日志消息输出出来那么我们就只需要将日志的输出等级设置为B就好了那么低于这个等级的C、D等级的日志就不会输出出来;支持同步写日志和异步写日志,刚才我们不是说本日志系统的核心是 ‘‘将一条日志消息写入到指定位置’’ 嘛怎么写入?这里就支持两种写入方式同步写入和异步写入;支持多个落地方向比如:支持将日志消息落地到标准输出、指定文件、滚动文件、数据库、网络服务器等等…支持多线程使用同一个日志器写日志也就是我们的日志系统是线程安全的除了上述我们指定的落地方向之外我们的日志系统还支持扩展用户自定义落地方向… 开发环境 Centos 7.6 vscode/vim/VS2019 g/gdb makefile 核心技术 继承和多态的应用 C11(多线程、auto、智能指针、右值引用、互斥锁等) 双缓冲区 生产者消费者模型 设计模式 可变参数 日志系统介绍 为什么需要日志系统? ⽣产环境的产品为了保证其稳定性及安全性是不允许开发⼈员附加调试器去排查问题,可以借助⽇ 志系统来打印⼀些⽇志帮助开发⼈员解决问题上线客⼾端的产品出现bug⽆法复现并解决,可以借助⽇志系统打印⽇志并上传到服务端帮助开发⼈员进⾏分析对于⼀些⾼频操作如定时器、⼼跳包在少量调试次数下可能⽆法触发我们想要的⾏为通过断 点的暂停⽅式我们不得不重复操作⼏⼗次、上百次甚⾄更多导致排查问题效率是⾮常低下可 以借助打印⽇志的⽅式查问题在分布式、多线程/多进程代码中出现bug⽐较难以定位可以借助⽇志系统打印log帮助定位 bug帮助⾸次接触项⽬代码的新开发⼈员理解代码的运⾏流程 日志系统框架设计 本项目实现的是一个多日志器的日志系统说白了也就是多种类型的日志器程序员可以通过使用我们的日志器来将指定日志消息按照指定格式和输出等级写入到指定位置 程序员再使用我们日志器的时候只需要初始化好日志器的格式化方式、日志输出等级、日志的实际落地方向、以及日志器的类型(同步写日志/异步写日志)就可以了之后就可以使用我们的日志器输出日志消息了 日志系统模块划分 首先我们在来重复一下本日志系统的核心功能:将一条日志消息按照指定格式化和输出等级写入到指定文件 由此根据本项目的核心功能我们可以完成日志系统的各个模块的肢解: 1. 日志消息模块 我们的日志消息不可能就是一个简单的字符串吧根据C面向对象的思想我们需要将用户传递给我们的日志消息包装成一个一个日志对象该日志对象中包含:该日志消息所在的文件、所产生的行号、所发生的时间、那个线程产生的这条日志消息、本条日志消息的等级、以及本条日志消息的主体(也就是用户告诉我们的日志消息)等这些信息基于C的OO思想我们需要将这西基本日志消息打包起来这样更符合我们C程序员对于面向对象的思想的理解、也是我们的程序代码更具有封装性、独立性、健壮性! 2. 格式化模块 我们最终写入到文件中的日志消息都是一个一个的字符串而不是一个个的日志对象因此我们需要一个专门将日志对象格式化成指定字符串的模块;我们只需要给该格式化类在初始化的时候指定好我们想要的格式化方式那么在后面的操作中我们只需要给该格式化类传递一个日志对象就能帮我们转换出一个指定格式的日志字符串 3. 日志等级模块 由于我们的日志系统是需要支持日志输出等级的限制的因此我们需要单独拎出来一个日志等级的模块该模块规定了我们的日志消息具有那些等级 4. 落地模块 现在格式化好的日志消息字符串已经有了那么我们就应该将这些格式化字符串写入到指定文件中去那么指定文件有那些? 根据我们刚开始所说我们支持将日志消息落地到:标准输出、指定文件、滚动文件等这几个方向因此我们在落地模块中针对标准输出我们可以封装一个落地方向、针对指定文件我们又可以封装一个落地方向、针对滚动文件我们又可以封装一个落地方向但是这些落地方向都需要提供一个同样的接口也就是落地接口,用户只要通过该接口就能将格式化日志信息落地到指定的落地方向; 5.写入方式模块(日志器模块) 上面几个模块中格式化日志字符串准备好了、落地方向也准备好了最终我们要完成的就是将格式化信息“写入”到实际的落地方向中可是怎么写啊? 根据我们目前的情况我们 写日志有两种情况:1、同步写2、异步写; 这两种写入方式,也就是两种不同的日志器:同步日志器、异步日志器; 该模块是对于:格式化模块、落地模块、日志消息模块的整合; 最终与用户直接交互的也是该模块! 用户可以通过改模块将日志消息通过指定接口输出到指定文件; 就比如:用户选择了同步日志器模块那么用户只需要通过该模块提供的接口就能将用户想要输出的日志信息通过该日志器以同步写入的方式落地到指定文件异步日志器也是一样的 以上是几个大模块当然不排除在实际开发过程中需要我们在细分几个小模块出来 以下是几个模块交互的简图: 代码实现 注意我们所实现的日志系统是能在Linux和Windows平台下都能跑的因此在某些地方我们可能需要使用到条件编译; __inux__表示在Linux环境下; _WIN32表示在Windows环境下; 通用工具实现 在具体开始设计各个模块之间我们需要先设计一些通用工具因为在后续模块的设计中我们需要使用这些工具: 我们需要设计那些工具? 1、获取当前时间戳; 2、获取文件目录;(比如:给你一个/a/b/c/t.txt,我们要获取的目录就是:/a/b/c/) 3、判断路径是否存在; 4、创建一个目录;(不是创建一个普通文件给你一个/a/b/c/t.txt路径你要创建出/a/b/c/目录) 基于上述操作我们设计的代码如下这里的设计我们不讲具体实现细节只讲大概思想: //util.hpp #pragma once #include iostream #include ctime #include string #include sys/stat.h #ifdef _WIN32 #includedirect.h #elif __linux__ #include sys/stat.h #endif // _WIN32// 这个文件中包含一些通用工具 // namespace MySpace {class util{public:// 1、获取当前时间戳static time_t getCurTime(){return time(nullptr);}// 2、获取文件目录static std::string getDirectory(const std::string pathname){int pos pathname.find_last_of(/\\);if (pos std::string::npos)return std::string(./);return std::string(pathname.begin(), pathname.begin() pos 1);}// 3、判断路径是否存在static bool isExist(const std::string pathname){struct stat buf;return !(stat(pathname.c_str(), buf) 0);}// 4、创建一个目录static bool createDirectory(const std::string pathname){if (pathname.empty())return false;if (isExist(pathname))return true;int pos 0;while (pos pathname.size()){pos pathname.find_first_of(/\\, pos);if (pos std::string::npos){//mkdir(pathname.c_str(), 0777); #ifdef _WIN32if (_mkdir(pathname.c_str()) -1)return false; #elif __linux__;if (mkdir(pathname.c_str(), 0777) -1)return false; #endif break;}// 0~pos1的需要创建std::string cur std::string(pathname.begin(), pathname.begin() pos 1);if (isExist(cur)){pos;continue;} #ifdef _WIN32if (_mkdir(cur.c_str()) -1)return false; #elif __linux__;if(mkdir(cur.c_str(),0777)-1)return false; #endif pos 1;}return true;}}; }日志等级模块实现 在该模块中我们需要规定各种日志等级因此我们可以采用枚举来实现同时我们需要提供一个将这些枚举常量转换为字符串的函数因为最后我们在使用的时候是需要将这些日志等级转换成字符串的 而我们的日志等级有: DEBUG:调试日志信息; INFO:正常日志信息; WARNING:警告日志信息; Err:错误日志信息; FATAL:致命日志信息; UNKNOWN:未知日志信息; OFF:关闭日志信息//当日志的输出等级为OFF的时候低于这个等级以下的日志信息都无法被输出 转换接口: levelToString();//将日志等级转换为字符串 代码实现: //level.hpp #pragma once // 日志等级设置模块 #include iostream #include string namespace MySpace {enum class LogLevel{DEBUG 0,INFO,WARNING,Err,FATAL,UNKNOWN,OFF};// 再提供一个函数将枚举等级转换为字符串std::string levelToString(MySpace::LogLevel lev){switch (lev){case MySpace::LogLevel::DEBUG:return std::string(DEBUG);case MySpace::LogLevel::INFO:return std::string(INFO);case MySpace::LogLevel::WARNING:return std::string(WARNING);case MySpace::LogLevel::Err:return std::string(Err);case MySpace::LogLevel::FATAL:return std::string(FATAL);case MySpace::LogLevel::OFF:return std::string(OFF);default:return std::string(UNKNOWN);}} }日志消息模块实现 该模块比较简单也就是设计一个日志消息类该日志消息类中主要包含以下几个要素信息: 日志消息所产生时间;日志消息所产生源文件;日志信息产生的行号;产生日志的线程号;日志等级;日志器名称;(当前日志消息是通过那个日志器输出的!)日志的主体信息 代码实现: //logMes.hpp #pragma once #include iostream #ifdef __linux__ #include pthread.h #endif #include string #include thread #include level.hpp /*日志消息模块:功能:封装一条日志信息 */ namespace MySpace {class LogMes{public:time_t _tm; // 日志消息所产生的时间std::string _fileName; // 日志信息所产生的源文件int _line; // 日志信息产生的行号std::thread::id _tid; // 产生日志的线程号MySpace::LogLevel _lev; // 日志等级std::string _loggerName; // 日志器名称std::string _payload; // 日志的主体信息LogMes() default;LogMes(const std::string filename, int line, MySpace::LogLevel lev, const std::string loggerName, const std::string payLoad): _tm(time(nullptr)), _fileName(filename), _line(line), _lev(lev), _tid(std::this_thread::get_id()), _loggerName(loggerName), _payload(payLoad){}~LogMes() {}}; } 格式化模块实现 该模块的主要功能就是将日志对象格式化成特定字符串; 同时提供用户自定义格式化字符串的功能; 因此在该模块中我们自定义一些格式化字符(注意与C语言的标准格式化字符区别这里定义的字符只针对我们本日志系统生效!): 注意如果我们想要根据明确的指定每个元素的打印格式的话我们可以在主格式化字符后面跟上{子格式化字符}的方式来进一步指明具体打印格式比如: %d格式字符表示我们要从日志消息中取出时间这个元素可是如果直接取出来的话那么打印的就是时间戳这样打印出来的格式明显不好看因此我们需要进一步细化时间的打印格式比如我们想按照:某时某分某秒 这样的格式打印时间那么我在指定话%d格式化字符的时候就必须像这样指明: %d{%H:%M:%S}这样:%d表示从日志器对象中取出时间元素{}表示按照花括号里面的格式打印指定元素; 就比如现在有一个日志对象: 对于格式化字串为:[%d{%H:%M:%S}] %m%n 则该日志对象对应的格式化信息为:[22:32:54] 创建套接字失败\n 因此我们的格式化类可以按照一下方向设计: 管理的成员: 格式化字符串; 提供接口 format();//接收外部的日志对象然后根据格式化字符串将日志对象转换成指定字符串; 为此格式化类的大致结构如下: 现在的问题就变成了我们应该如何去编写这个format格式化字符函数? 首先format的功能是干什么? 就是根据格式化字符从日志对象中拿出指定元素来并且按照格式化字符的顺序来进行排列组合! 说白了也就是按照格式化字符串中格式化字符的顺序来从日志对象中拿取指定元素; 因此这里有一个思路: 1、便分析格式化字符串边从日志对象中获取元素; 具体思想就是:我们可以边分析格式化字符串然后从日志对象中获取数据: 就比如:%d%m%c%n 我们首先分析到%d字符串那么我们此时就从日志对象中取出时间元素来插入一个临时字符串中; 接着我们又分析到%m字符串那么我们此时就从日志对象中取出日志主体消息元素来插入到临时字符串中 紧接着我们有分析到%c字符串那么表示我们此时需要从日志对象中取出一个日志器名称元素来插入到临时字符串中 最后分析到%n字符串时表示我们需要向零时字符串中插入一个’\n’; 最后我们给外部返回这个临时字符串就好了 上面的方法可以吗?当然可以没有任何问题 可是似乎效率上有一点浪费就比如如果我用同一个格式化类格式化多个日志对象那么我们就需要每次都对格式化字符串做一次遍历分析有点划不来因为这个格式化字符串是一样的啊我们每格式化一个日志对象就要对一格式化字符串做一次分析还是同一个格式化字符串这不就是一种浪费吗为此我们需要想一种比较高效的格式化方法 唉实际上格式化的过程不就是按照某一种顺序从日志对象中拿数据然后插入到临时字符串中吗 这个“顺序”哪里来 不就是通过分析格式化字符串而来吗 要是我们能够“保存”一下这个“取值顺序”那么以后我们再格式化日志对象的时候我们就只需要遍历这个顺序去日志对象中取元素不就完成了日志对象的格式化了吗而不用每次都花大量的精力去遍历分析格式化字符串你品你细品是不是很有道理 那么具体应该怎么做呢? 在C中万物皆可对象化我们使用一个个对象来形成对应的取值: 比如 %d字符对应一个子格式化对象TimeFormatItem,该格式化对象表示从日志对象中获取时间元素 %t字符对应一个子格式化对象ThreadFormatItem,该格式化对象表示从日志对象中获取线程ID %p字符对应一个子格式化对象LevelFormatItem,该格式化对象表示从日志对象中获取⽇志等级 %c字符对应一个子格式化对象NameFormatItem,该格式化对象表示从日志对象中获取⽇志器名称 %f字符对应一个子格式化对象CFileFormatItem,该格式化对象表示从日志对象中获取源码所在⽂件名 %l字符对应一个子格式化对象CLineFormatItem,该格式化对象表示从日志对象中获取源码所在⾏号 %m字符对应一个子格式化对象MsgFormatItem,该格式化对象表示从日志对象中获取有效⽇志数据 %n字符对应一个子格式化对象NLineFormatItem,该格式化对象表示需要提供一个’\n’换行 %T字符对应一个子格式化对象TabFormatItem,该格式化对象表示需要提供一个tab缩进! 其它非格式化字符对应一个子格式化对象OtherFormatItem,该格式化对象表示原样输出非格式化字符; 我们再来举个简单例子来理解一下我们想要干什么: 就比如现在有一个格式化字符串:[%d%m%n] 现在我们分析到:“[“非格式化字符串我们就会形成一个OtherFormatItem 对象; 然后分析到%d:形成一个TimeFormatItem对象 分析到%m:形成一个MsgFormatItem对象; 分析到%n:形成一个NLineFormatItem对象; 分析到非格式化字符:”]”,形成一个OtherFormatItem 对象 然后我们利用一个容器将这些对象给保存起来也就是将这些顺序保存起来然后我们在格式化日志对象的是否只需要遍历这个容器就能得到指定格式的字符串 为此这个子格式化对象必须提供一个接口: 该接口具备从日志对象中获取指定元素的能力并且能够将获取到的指定元素转换为字符串返回给外部这样外部就能直接将得到的字符串插入进临时字符串 同时为了更好的利用容器来管理这些子格式化类我们可以考虑使用继承的体系来设计这些子格式化类这样的话我们在利用容器管理这些类的时候就能利用父类指针或引用来管理这些子格式化对象了 为此我们重新设计一下Formater类: 管理成员: 1、格式化字符串 2、vector数组;//用来存放“取值顺序” 提供接口: format();//用于暴露给外部提供格式化的接口; parsePattern();//不暴露给外部用来专门进行格式化字符串分析并形成对应子格式化对象的接口; createItem();//不暴露给外部专门用来创建格式化子对象的 父类:子格式类FormatItem 提供虚接口: format();//功能从格式化对象中取出指定元素并格式化成字符串返回给外部 具体从日志对象中取出什么由具体的派生对象来决定: //format.hpp #pragma once /* 格式化模块: 功能:将一个日志对象格式化成一个字符串接口设计: 1、构造时告诉格式化对象格式化字符串; 2、format接口:给这个接口传递一个日志对象将其转换成格式化字符串格式化接口设计:本质就是按照一定顺序从日志对象中取出对应元素来插入字符串中 比如%d%m%t 就是告诉我们先从日志对象中取出时间元素、再取出消息主体元素、再取出线程id元素;要是我们能够将这个顺序保存起来那么后面再格式化日志对象的时候我们只需要遍历这个顺序从日志对象中取出对应元素即可因此格式化类的属性包含两个: 1、string :保存格式化字符串; 2、一个容器:保存日志对象的取值“顺序” 如何得到这个顺序? 分析格式化字符串-得到一个格式化字符创建一个子格式化对象,该子格式化对象表示从日志对象中获取特定元素; 比如:%d%m%t; 1、形成%d的子格式化对象,该格式化对象表示从日志对象中获取时间元素; A 2、形成%m的子格式化对象,该格式化对象表示从日志对象中获取主体消息元素; B 3、形成%t的子格式化对象,该格式化对象表示从日志对象中获取线程id元素; C我们得将分析出来的ABC对象保存起来然后后面再格式化日志对像的时候就不用再分析格式化字符串,而是直接遍历ABC三个对象按照一定顺序从日志对象中获取元素了得用一个公共类型来接收ABC三个对象考虑继承(利用父类指针或引用来接收这些对象) 这样顺序如何保存的问题得以解决!元素也获取到了但是最后我们是要的字符串啊因此父类提供一个虚函数该虚函数专门从日志对象中获取指定元素然后转换成字符串的接口 */ #include iostream #include string #include vector #include memory #include sstream #include time.h #include unordered_set #include logMes.hpp #include util.hppnamespace MySpace {// 格式化类class Formater{private:// 子格式化对象的公共父类写出来是为了方便保存顺序,同时也方便后续使用统一接口输出...class FormatItem{public:virtual ~FormatItem() {};// 公共接口从日志对象中获取指定元素并格式化成字符串virtual std::string format(const MySpace::LogMes) 0;};// 开始派生...//%m对应的子格式化对象class MsgFormatItem : public FormatItem{public:// 从日志对象中获取主体消息std::string format(const MySpace::LogMes mes) override{return mes._payload;}};//%p对应的子格式化对象class LevelFormatItem : public FormatItem{public:// 从日志对象中获取日志等级元素,并转换为字符串std::string format(const MySpace::LogMes mes) override{return levelToString(mes._lev);}};//%c对应的子格式化对象class NameFormatItem : public FormatItem{public:// 从日志对象获取日志器名称std::string format(const MySpace::LogMes mes) override{return mes._loggerName;}};//%t对应的子格式化对象class ThreadFormatItem : public FormatItem{public:// 从日志对象获取线程id元素std::string format(const MySpace::LogMes mes) override{std::stringstream fstr;fstr mes._tid;return fstr.str();}};//%d对应的子格式化对象class TimeFormatItem : public FormatItem{private:std::string _childFmt;public:TimeFormatItem(const std::string Cfmt %H:%M:%S) : _childFmt(Cfmt) {}// 从日志对象获取时间元素,并按照%d字符对应的子格式转换为字符串// eg: %d - %H:%M:%Sstd::string format(const MySpace::LogMes mes) override{time_t t mes._tm;char buffer[1024];struct tm tmp;// 将时间戳现转换为时间结构体#ifdef _WIN32localtime_s(tmp, t); #elif __linux__localtime_r(t,tmp); #endif // 将时间结构体转换为格式化字符串strftime(buffer, sizeof(buffer), _childFmt.c_str(), tmp);return buffer;}};//%f对应的子格式化对象class CFileFormatItem : public FormatItem{public:// 从mes对象中获取文件名std::string format(const MySpace::LogMes mes) override{return mes._fileName;}};//%l对应的子格式化对象class CLineFormatItem : public FormatItem{public:// 从mes对象中获取行号std::string format(const MySpace::LogMes mes) override{return std::to_string(mes._line);}};//%T对应的子格式class TabFormatItem : public FormatItem{public:// 输出tabstd::string format(const MySpace::LogMes mes) override{return ;}};//%n对应的子格式化对象class NLineFormatItem : public FormatItem{public:// 输出换行std::string format(const MySpace::LogMes mes) override{return \n;}};// 非格式化字对应的符子格式化对象(原样输出)class OtherFormatItem : public FormatItem{public:OtherFormatItem(const std::string str) : _oldStr(str) {}// 原样输出非格式化字符std::string format(const MySpace::LogMes mes) override{return _oldStr;}private:std::string _oldStr;};private:// 分析格式化字符串的工作bool parsePattern(){char key -1;std::string val;int pos 0;while (pos _fmt.size()){val.clear();while (pos _fmt.size() _fmt[pos] ! %)val _fmt[pos];key -1;_items.push_back(createItem(key, val));//没有遇到格式化字符if(pos_fmt.size())return true;//%后面没有字符了错误格式化字符串if (pos 1 _fmt.size())return false;if (_fmt[pos 1] %){key -1, val %;_items.push_back(createItem(key, val));pos 2;continue;}// 检查是不是合法格式字符key _fmt[pos 1];auto it _fmtSet.find(key);//%x这种错误格式化字符if (it _fmtSet.end())return false;if (pos 2 _fmt.size() || _fmt[pos 2] ! {){val ;pos 2;}else{int cur pos 3;while (cur _fmt.size() _fmt[cur] ! })cur;// 没找到}if (cur _fmt.size())return false;// 找到了}val std::string(_fmt.begin() pos 3, _fmt.begin() cur);pos cur 1;}_items.push_back(createItem(key, val));}return true;}// 根据格式化字符创建对应的格式化对象 %d{%H:%M:%S}可能会出现这种情况:key%d,val%H:%M:%Sstd::shared_ptrFormatItem createItem(const char key, const std::string val){switch (key){case d:if (val )return std::make_sharedTimeFormatItem();elsereturn std::make_sharedTimeFormatItem(val);case T:return std::make_sharedTabFormatItem();case t:return std::make_sharedThreadFormatItem();case p:return std::make_sharedLevelFormatItem();case c:return std::make_sharedNameFormatItem();case f:return std::make_sharedCFileFormatItem();case l:return std::make_sharedCLineFormatItem();case m:return std::make_sharedMsgFormatItem();case n:return std::make_sharedNLineFormatItem();default:return std::make_sharedOtherFormatItem(val);}}public:Formater(const std::string fmt [%d{%H:%M:%S}]%T[%t]%T[%p]%T[%c]%T[%f:%l]%T%m%n) : _fmt(fmt){// 分析格式化字符串形成取值顺序,方便后续格式化操作if (!parsePattern())throw std::string(格式化字符串错误!);}// 将日志对象格式化的接口std::string format(const MySpace::LogMes mes){std::string ret; // 记录格式化字符串for (auto e : _items){ret e-format(mes);}return ret;}static std::unordered_setchar _fmtSet; // 用来记录格式化字符,所有formater类都要共享该集合为避免每次都创建故设为静态private:std::string _fmt; // 格式化字符串std::vectorstd::shared_ptrFormatItem _items; // 用来记录取值顺序};std::unordered_setchar Formater::_fmtSet { d, T, t, p, c, f, l, m, n }; } 落地模块实现 该模块的主要任务是完成日志信息的实际落地也就是用户给该模块一个格式化好的日志信息该模块直接将其落地当指定文件 本日志系统提供标准输出、指定文件、滚动文件这几个落地方向 同时也支持扩展其它落地方向比如:网络服务器、数据库 对于该模块的实现我们打算采用继承工厂模式的方式来实现; 因此我们需要首先抽象出一个:抽象落地方向 该抽象落地方向提供一个虚接口outLog()用户通过该接口来完成实际的落地; 具体实现如下: sink.hpp #pragma once /* 日志落地模块 每一个类对应一个落地方向 落地方向: 1、标砖输出 2、指定文件 3、滚动文件(以大小滚动、以时间滚动...) 目前就这样... 扩展:网络服务器、数据库设计思想为了方便扩展 可以采用继承的设计层次 1、先抽象出一个抽象基类(该基类有一个接口专门实现具体落地的) 2、根据基类派生出具体落地方向...如果要扩展的话那么可以继承基类重写落地接口 3、采用工厂模式来创建落地方向,封装创建细节让用户使用起来更爽 */ #includeiostream #includestring #includefstream #includetime.h #includeutil.hpp #includesock.hpp namespace MySpace {class Sink{public:virtual ~Sink() {};//具体落地接口virtual bool outLog(const char*,int ) 0;};//落地方向是标准输出class StdoutSink :public Sink{public:bool outLog(const char*str,int len)override{std::cout.write(str,len);//std::string str1(str,strlen);//std::cout str1 std::endl;bool flaf !std::cout.fail();return flaf;}};//落地方向是一个具体文件class FileSink :public Sink{private:std::string _fileName;std::ofstream _of;//利用文件流管理文件public:FileSink(const std::string filename) :_fileName(filename) {//先保文件所在目录存在MySpace::util::createDirectory(MySpace::util::getDirectory(_fileName));//追加写的方式打开文件文件不存在则创建_of.open(_fileName, std::ios::out | std::ios::app);}//析构的时候关闭文件~FileSink() { _of.close(); }bool outLog(const char* str, int len)override{_of.write(str,len);return !_of.fail();}};//落地方向是滚动文件(以大小为间隔)class RollSinkBySize :public Sink{private:std::string _baseName;//滚动文件的基本名称std::ofstream _of;//文件流来管理一个文件size_t _curSize;//当前文件大小const size_t _MaxSize;//单个文件总大小size_t _count;//创建的文件的个数std::string createFileName(){std::string ret(_baseName);ret -v;ret std::to_string(_count);ret .log;return ret;}public:enum class FileSize :size_t{Small_Size 512 * 1024,//512kMiddle_Size 1 * 1024 * 1024,//1MBig_Size 10 * 1024 * 1024//10M};RollSinkBySize(const std::string baseName, FileSize MaxSzie (FileSize::Small_Size)):_baseName(baseName), _MaxSize(static_castsize_t(MaxSzie)), _curSize(0), _count(1){//先保证文件所在路径存在MySpace::util::createDirectory(MySpace::util::getDirectory(_baseName));//根据基本文件名_v[count]的方式来形成新文件名std::string fileName createFileName();_of.open(fileName, std::ios::out | std::ios::app);}bool outLog(const char* str, int len)override{//该换文件了if (_curSize _MaxSize){std::string ret createFileName();_of.close();_curSize 0;_of.open(ret, std::ios::out | std::ios::app);}_of .write (str,len);_curSize len;return !_of.fail();}};//落地方向:滚动文件(通过过时间)class RollSinkByTime :public Sink{private:std::string _baseName;//滚动文件的基本名称std::ofstream _of;//文件流来管理一个文件size_t _timeGap;//时间间隔time_t _createTime;//文件创建时间std::string createFileName(){//扩展名xxxx-xx-xx_H:M:S.logtime_t curTime time(nullptr);struct tm tmp; #ifdef _WIN32localtime_s(tmp, curTime); #elif __linux__localtime_r(curTime, tmp); #endif // _WIN32char buffer[128];snprintf(buffer, sizeof(buffer), %04d%02d%02d%02d%02d%02d.log, tmp.tm_year 1900, tmp.tm_mon 1, tmp.tm_mday, tmp.tm_hour, tmp.tm_min, tmp.tm_sec);std::string ret(_baseName);ret buffer;return ret;}public:enum class TimeGap{Samll_Time 1,Middle_Time 3600,Big_Time 24 * 3600};RollSinkByTime(const std::string baseName, TimeGap tm TimeGap::Samll_Time) :_baseName(baseName), _timeGap(static_castsize_t (tm)){// 先保证文件所在路径存在MySpace::util::createDirectory(MySpace::util::getDirectory(_baseName));std::string fileName createFileName();_of.open(fileName, std::ios::out | std::ios::app);}bool outLog(const char*str,int len)override{//大于设置时间间隔if (time(nullptr) - _createTime _timeGap){std::string ret createFileName();_of.close();_of.open(ret, std::ios::out | std::ios::app);}_of .write( str,len);return !_of.fail();}};//扩展1:网络服务器//扩展细节:可以考虑使用多路复用...//采用工厂模式,封装落地对象创建细节class SinkFactory{public:templateclass T, class ...Argsstatic std::shared_ptrT createSink(Args...args){return std::make_sharedT(args...);}}; }这样的话我们就可以利用工厂对象创建出指定落地方向然后利用outLog接口完成指定输出: 日志器模块 该模块是针对于前几个模块的整合也是直接面向客户所使用的对于该模块的实现我们基于:继承建造者设计模式来实现; 因此我们需要抽象出一个日志器抽象基类 该基类提供的接口如下: 1、 debug();//站在用户的角度来说就是我只需要调用该接口就能完成debug版本日志消息的输出但是站在日志器的角度来看就是我日志器通过该接口先收集用户的日志信息然后根据输出等级来决定是不是要实际落地这条消息如果当前消息高于输出等级限制那么我们会利用该日志消息封装一个更为详细的一个日志对象然后利用日志器的格式化对象格式化成指定字符串最后在完成实际落地 同理对于info、warning、error、fatal、unknown这几个接口的实现也是一样的 2、 该基类还需要提供一个完成实际落地的接口log();来完成实际落地该接口根据不同的日志器采用不同的实现因此该接口必须是虚接口 管理成员: 格式化对象 vector数组用来存放各种落地方向 日志输出限制等级 互斥锁保证日志器是线程安全的 日志器名称;(用于唯一标识一个日志器) 为此。基类日志器代码如下: 同步日志器 基于抽象基类所作的工作同步日志器所做的工作就很简单同步日志器只需要重写log接口完成实际落地就可以了: 同步日志器的落地方式也是比较简单的就是直接向落地方向中写入就好: 为此同步日志器的设计代码如下: 异步日志器 异步日志器呢就比较复杂一点了为什么呢 因为异步日志器与同步日志器不同异步日志器不会完成日志消息的实际落地异步日志器是将自己的日志消息落地到一个“缓冲区”中由异步线程完成从缓冲区中取出数据来完成实际落地 下面我们来具体聊一聊异步日志器的工作流程: 首先我们的工作线程落地不在向实际方向落地而是向“缓冲区”中落地由我们的异步线程来从缓冲区中取出数据来完成实际落地 因此这是一个典型的生产者消费者模型在这个过程中我们需要维护生产者生产者 生产者消费者之间的互斥关系因此我们需要一把锁 同时需要两个条件变量分别用来维护生产者消费者之间的同步关系当缓冲区满了我们需要阻塞生产者线程让消费者线程来当缓冲区为空了我们需要阻塞消费者线程让生产者来进行生产活动 这些都没问题可是在实际开发中锁与锁的冲突无疑是比较严重的因为这把锁每时每刻除了有生产者和生产者在竞争也有生产者和消费者在竞争竞争是比较激烈的这样的话就会导致整个异步日志器的工作效率比较低为此为了减少锁的冲突提高效率我们可以采用双缓冲区的作法: 双缓冲区的工作流程是这样的工作线程呢现在将数据全部放进生产缓冲区中而异步线程只从消费缓冲区中拿数据只有当消费缓冲区中的数据没了的时候我们才交换两个缓冲区这时候我们消费者才需要去申请锁而在一般情况下我们生产者和消费者都不是关心的同一个缓冲区我们自然也就不必每次都去申请锁这样就减少了生产者和消费者之间的锁冲突提高了整体工作效率 当然如果在消费缓冲区为空了同时生产缓冲区也为空了那么就没有交换的必要了我们就阻塞异步线程 如果生产缓冲区被打满了那么我们也就可以阻塞生产者让消费者继续消费一会 缓冲区实现 上面交代了异步日志器工作流程最为异步日志器必不可少的一环我们先来实现缓冲区 该缓冲区采用面向字节流的设计思想不需要设计成为一个个string类型的缓冲区因为设计成string类型的话异步线程在取的时候就只能取一个落地一个会增加IO次数降低IO效率同时异步线程在取string字符串的时候需要自己先定义一个string缓冲区在来取这又是一次没必要拷贝为此我们的缓冲区采用面向字节流的思想用户可以自主决定一次性从缓冲区中读取多少字节的数据 该缓冲区提供的功能: write();用户只需要调用write接口就能向缓冲区中写入数据 begin返回可读位置的指针注意这里并没有实现read()接口如果实现该接口的话那么上层在读取的时候必须先定义一个缓冲区来读取这势必会造成一次不必要的拷贝 AbleReadSize可读数据长度 AbleWriteSize()可写数据长度; moveRead();移动读指针 moveWrite();移动写指针; reset();将读写指针置0交换缓冲区之前需要对消费缓冲区做的动作 swap();//交换两个缓冲区; 管理的成员: vector 读指针 写指针 为此缓冲区具体实现如下: //buffer.hpp #pragma once /* 异步缓冲区 功能:暂时接收工作线程发送的日志信息提供接口: write接口:外界向缓冲区插入数据 begin();//可写位置指针 AbleWriteSize();//可写空间 AbleReadSize();//可读空间 moveRead()/moveWrite();//读写指针移动 reset();//复位 swap();//交换 成员: vectorchar read_index; write_index; */ #include iostream #include vector #include cassert namespace MySpace { #define DEFAULT_BUFFER_SIZE 10 * 1024 * 1024 // 10M #define DEFAULT_BUFFER_THRESHOLD 80 * 1024 * 1024 // 80M//阈值 #define DEFAULT_BUFFER_INCREMENT 10 * 1024 * 1024 // 10M//增量class Buffer{public:Buffer(size_t cap DEFAULT_BUFFER_SIZE) : _q(cap) {}bool write(const char *str, int len){// 如果可写空间不够?//有两种作法:1、扩容//2、由外部直接在插入执前做插入判断如果实际插入的大小AbleWriteSize()那么就直接插入即可如果大于了那么由外部进行阻塞//这里的话我们缓冲区提供的是扩容操作//但是实际会不会扩容完全由用户控制//如果用户做插入检查那么就不会发生扩容//如果用户不做插入检查那么就会发生扩容EableEnoughSpace(len);// 进行拷贝std::copy(str, str len, _q.begin() _write_index);// 更新write指针moveWrite(len);return true;}const char *begin(){return _q[_read_index];}// 可写空间size_t AbleWriteSize(){return _q.size() - _write_index;}// 可读空间大小size_t AbleReadSize(){return _write_index - _read_index;}void swap(Buffer bf){_q.swap(bf._q);std::swap(_write_index, bf._write_index);std::swap(_read_index, bf._read_index);}void reset(){_write_index _read_index 0;}void moveRead(size_t len){assert(len _read_index _write_index);_read_index len;}bool empty(){return _write_index _read_index;}private:void moveWrite(size_t len){assert(_write_index len _q.size());_write_index len;}void EableEnoughSpace(int len){if (len AbleWriteSize()){//扩容也是有讲究的//在没到达阈值之前快速扩//达到阈值过后线性扩size_t newSize _q.size() DEFAULT_BUFFER_THRESHOLD ? 2 * _q.size() : _q.size() DEFAULT_BUFFER_INCREMENT;_q.resize(newSize);}}std::vectorchar _q;size_t _read_index 0;size_t _write_index 0;};} // namespace MySpace 异步工作器实现 异步工作器模块实际上就是双缓冲区与异步线程的整合 工作线程通过与异步线程交互来完成异步日志器的工作输出; 异步工作器提供的功能: push:工作线程通过该接口向缓冲区添加数据 stop();工作线程可以通过调用该接口来停止异步工作器的工作; 管理成员: 双缓冲区; 互斥锁; 两个条件变量; _stop;停止标志 回调函数(也就是实际处理逻辑消费数据怎么处理由外部决定) 异步工作线程 具体实现代码如下: //looper.hpp #pragma once #include buffer.hpp /* 异步工作器模块: 功能:异步处理主线程的日志消息完成日志消息的实际落地 提供接口: stop();//停止异步工作器 push();//方便工作线程向异步工作器提供数据 成员: 1、双缓冲区(生消 2、互斥锁(保护生产者生产者 消费者生产者) 3、双条件变量(双缓冲区都为空,则阻塞异步线程;若生产缓冲区不够了阻塞生产者) 4、异步线程 5、停止标志 6、实际处理逻辑(具体如何实现交给外部来决定实现解耦) */ #include atomic #include thread #include mutex #include condition_variable #include functional namespace MySpace {using func_t std::functionvoid(Buffer );class AsyncLooper{public:AsyncLooper(const func_t f, bool safe true) : _work(f), _stop(false), _t(AsyncLooper::threadRuning, this), _safe(safe){}~AsyncLooper(){stop();}void stop(){_stop true;_conCond.notify_all();_t.join();}void push(const char *str, int len){if (_stop)return;{std::unique_lockstd::mutex locker(_mux);// 唤醒条件:或者实际写入的长度小于可写入的长度// 安全模式下执行if (_safe){_proCond.wait(locker, []() - bool{ return len _proBuf.AbleWriteSize(); });}// 非安全模式下直接插入不考虑空间问题少了会自动扩容_proBuf.write(str, len);}// 唤醒消费者,有数据来消费了_conCond.notify_all();}private:void threadRuning(){while (true){// 尝试交换if (_conBuf.empty()){std::unique_lockstd::mutex locker(_mux);// 唤醒条件:生产者队列不为空或者_stop标志被置true// 阻塞条件:生产者队列为空并且_stop置false;_conCond.wait(locker, []() - bool{ return _proBuf.empty() false || _stop; });if (_stop _proBuf.empty())break;// 生产者缓冲区有数据被唤醒_conBuf.reset();_conBuf.swap(_proBuf);}_proCond.notify_all();_work(_conBuf);}_proCond.notify_all();}bool _safe true;Buffer _proBuf; // 生产缓冲区Buffer _conBuf; // 消费缓冲区std::mutex _mux;std::condition_variable _proCond; // 生产者条件队列std::condition_variable _conCond; // 消费者条件队列std::atomicbool _stop; // 停止标志std::thread _t; // 异步线程func_t _work; // 实际处理逻辑}; } // nam;espace 在该异步工作器模块中我们提供了两种模式安全模式和非安全模式 处于非安全模式下的生产者线程在向缓冲区插入数据的时候不会发生扩容 而处于安全模式下的生产者线程在向缓冲区插入数据的时候不会发生扩容因为会被阻塞 回归异步日志器模块 现在准备工作已经做完了我们只需要在异步日志器中包含一个异步工作器模块就行了之后所有工作都交给工作线程和异步工作器这两个模块来完成因此异步工作器的log落地函数实际是将信息落地到缓冲区 同时异步日志器需要给异步工作器提供一个实际的工作函数来给异步工作器提供实际的工作处理 为此异步日志器模块设计如下: 建造者模式 现在同步日志器与异步日志器都已经建立好了但是每次使用之前都需要我们自己将日志器所需要的零件构造好很是麻烦为此我们可以采用兼着做模式来帮我们完成零件构造的过程; 首先抽象出一个抽象建造者: 该建造者可以帮我们建造任何零件; 同时该建造者还具有根据零件完成组装的功能这个根据具体的建造者建有关系因此我们需要提供一个组装接口该接口是虚接口: 日志器管理模块实现 上面的功能已经差不多将日志系统的大厦建立起来了可是上面建立的日志器只能在当前作用域使用要是在其它作用域使用的话那么就需要我们来传参完成使用起来非常不方便 可不可以设计一种管理器该管理器可以将所创建出来的日志器都管理起来同时这个管理器是全局唯一的哪里都可以使用我们只需要通过向该管理器告知日志器名称就能返回给我们指定日志器答案是可以的! 该日志器管理类: 1、管理已创建的日志器 2、可以同通过该管理器在全局任意地方根据日志器名称获取日志器 3、该管理器默认提供一个标准输出的同步日志器 4、该管理器为单例对象全局只有一个管理器对象 提供接口: getInstance();//获取日志管理器对象 addLogger();//添加日志器 isExists();//日志器是否存在 getLogger();//获取日志器 getRootLogger();//获取默认日志器 管理成员; unordered_mapstring ,shared_ptr _loggers; mutex保证日志管理器对象的线程安全 默认日志器对象 (同步标输出) 该管理器类设计如下: 可是这样还不够方便我们通过LocalLoggerBuilder建造出来的日志器需要我们自己手动添加进管理器很麻烦为此我们可以专门设计一个建造者该建造者会自动将建造出来的日志器添加进日志器管理对象中: 该日志器建造者具体实现如下: 日志器模块具体实现 至此日志器模块算是结束了接下来附上整个日志器模块的代码: //logger.hpp #pragma once /* 日志器模块或者说写入方式模块 日志器是直接暴露给用户的 功能:完成用户交给日志器的具体日志打印工作 设计思想: 为此站在用户的角度来说就是 我只要将日志消息传递给日志器日志器就能帮助我们完成日志的输出 因此日志器要提供一些接口来供用户传入日志消息 而站在日志器的角度来说我通过上诉接口获取到了用户的日志信息日志器要做的工作就是 根据用户传进来的日志信息封装一个日志对象 然后利用格式化类对象格式化日志对象 然后再使用一个落地接口完成最终落地一个日志器的落地方向可能不止一种为此我们需要一个容器来保存落地方向 日志器也需要知道如何格式化对象,为此他需要一个格式化对象 多线程情况下有可能多个线程使用同一个日志器对同一个落地方向进行输出会造成线程安全需要一把锁 日治器也需要一个限制等级来维护日志器正对那些等级的日志进行实际落地那些日志不进行输出; 日志器也需要一个名字来标识唯一日志器如果具体细分的话日志器分为:同步日志器、异步日志器 为此为了方便管理日志器可以采用继承的设计层次 */ #include util.hpp #include level.hpp #include format.hpp #include sink.hpp #include looper.hpp #include mutex #include stdarg.h #include unordered_map namespace MySpace {// 先抽象出一个基类日志器class Logger{public:virtual ~Logger() {}enum class LoggerType{LOGGER_SYNC, // 同步日志器LOGGER_ASYNC // 异步日志器};// 提供日志器基本功能public:Logger(const MySpace::Formater fmt, const std::vectorstd::shared_ptrMySpace::Sink sinks, const std::string name,MySpace::LogLevel lev LogLevel::DEBUG, LoggerType type LoggerType::LOGGER_SYNC): _fmter(fmt), _sinks(sinks), _levelLimits(lev), _mux(), _loggerName(name){if (_sinks.empty())_sinks.push_back(std::make_sharedStdoutSink());}const std::string name(){return _loggerName;}// 站在用户的角度是我只要想给接口提供日志信息就能完成debug等级的输出void debug(const std::string fileName, size_t line, const char* fmat, ...){// 站在日志器的角度来说我收到了用户的日志信息我需要将其加工成日志对象然后格式化然后完成实际落地// 0、先判断是否大于当前限制等级// 1、提取出日志消息主体;// 2、构造日志对象// 3、格式化va_list ap;va_start(ap, fmat);std::string str;// 完成0~3的工作if (!forwardRoll(fileName, line, LogLevel::DEBUG, str, fmat, ap)){va_end(ap);return;}va_end(ap);// 4、实际落地log(str);}void info(const std::string fileName, size_t line, const char* fmat, ...){va_list ap;va_start(ap, fmat);std::string str;// 完成0~3的工作if (!forwardRoll(fileName, line, LogLevel::INFO, str, fmat, ap)){va_end(ap);return;}va_end(ap);// 4、实际落地log(str);}void warning(const std::string fileName, size_t line, const char* fmat, ...){va_list ap;va_start(ap, fmat);std::string str;// 完成0~3的工作if (!forwardRoll(fileName, line, LogLevel::WARNING, str, fmat, ap)){va_end(ap);return;}va_end(ap);// 4、实际落地log(str);}void error(const std::string fileName, size_t line, const char* fmat, ...){va_list ap;va_start(ap, fmat);std::string str;// 完成0~3的工作if (!forwardRoll(fileName, line, LogLevel::Err, str, fmat, ap)){va_end(ap);return;}va_end(ap);// 4、实际落地log(str);}void fatal(const std::string fileName, size_t line, const char* fmat, ...){va_list ap;va_start(ap, fmat);std::string str;// 完成0~3的工作if (!forwardRoll(fileName, line, LogLevel::FATAL, str, fmat, ap)){va_end(ap);return;}va_end(ap);// 4、实际落地log(str);}void unknown(const std::string fileName, size_t line, const char* fmat, ...){va_list ap;va_start(ap, fmat);std::string str;// 完成0~3的工作if (!forwardRoll(fileName, line, LogLevel::UNKNOWN, str, fmat, ap)){va_end(ap);return;}va_end(ap);// 4、实际落地log(str);}private:bool forwardRoll(const std::string fileName, size_t line, LogLevel lev, std::string outBuffer, const char* fmat, va_list ap){if (lev _levelLimits)return false;// 提取有效载荷消息char buffer[4096];vsnprintf(buffer, sizeof(buffer), fmat, ap);// 构建日志对象LogMes mes(fileName, line, lev, _loggerName, buffer);// 格式化outBuffer _fmter.format(mes);return true;}protected:// 完成实际落地// 根据不同类型日志器完成不同的落地操作virtual void log(const std::string str) 0;MySpace::Formater _fmter; // 用来记录当前日志器的格式化格式std::vectorstd::shared_ptrMySpace::Sink _sinks; // 记录实际落地方向std::mutex _mux; // 保护日志器的线程安全MySpace::LogLevel _levelLimits; // 日志限制输出等级std::string _loggerName; // 日志器名称LoggerType _type;};// 具体派生出一个同步日志器class SyncLogger : public Logger{public:SyncLogger(const MySpace::Formater fmt, const std::vectorstd::shared_ptrMySpace::Sink sinks, const std::string name,MySpace::LogLevel lev LogLevel::DEBUG) : Logger(fmt, sinks, name, lev, LoggerType::LOGGER_SYNC) {}virtual void log(const std::string str) override{std::lock_guardstd::mutex locker(_mux);for (auto e : _sinks)e-outLog(str.c_str(), str.length());}};// 具体派生出一个异步日器class AsyncLogger : public Logger{public:AsyncLogger(const MySpace::Formater fmt, const std::vectorstd::shared_ptrMySpace::Sink sinks, const std::string name,MySpace::LogLevel lev LogLevel::DEBUG, bool safe true) : Logger(fmt, sinks, name, lev, LoggerType::LOGGER_SYNC), _looper(std::bind(AsyncLogger::realLog, this, std::placeholders::_1), safe){}virtual void log(const std::string str) override{_looper.push(str.c_str(), str.size());}private:void realLog(Buffer bf){for (auto e : _sinks)e-outLog(bf.begin(), bf.AbleReadSize());bf.moveRead(bf.AbleReadSize());}MySpace::AsyncLooper _looper;};// 创建日志器所需要的零件class Builder{public:virtual ~Builder() {}void buildLoggerName(const std::string name){_loggerName name;}void buildLoggerType(Logger::LoggerType type Logger::LoggerType::LOGGER_SYNC){_type type;}void buildFormater(const std::string fmt ){if (fmt )_fmt Formater();else_fmt Formater(fmt);}void buildLevelLimits(LogLevel lev LogLevel::DEBUG){_levelLimits lev;}// 建造异步建造器时时所使用void EnableSafe(bool flag){_safe flag;}template class T, class... Argsvoid buildSink(Args... args){_sinks.push_back(SinkFactory::createSinkT(args...));}// 根据零件组合一个具体对象virtual std::shared_ptrLogger build() 0;protected:bool _safe true; // 异步日志器建造需要(默认安全)std::string _loggerName;Logger::LoggerType _type;Formater _fmt;MySpace::LogLevel _levelLimits;std::vectorstd::shared_ptrMySpace::Sink _sinks;};// 局部日志器建造class LocalLoggerBuilder : public Builder{public:virtual std::shared_ptrLogger build() override{std::shared_ptr std::vectorstd::shared_ptrMySpace::Sink sp(_sinks, [](std::vector std::shared_ptrMySpace::Sink*ptr)-void {ptr-clear();});if (_type Logger::LoggerType::LOGGER_SYNC)return std::make_sharedSyncLogger(_fmt, _sinks, _loggerName, _levelLimits);elsereturn std::make_sharedAsyncLogger(_fmt, _sinks, _loggerName, _levelLimits, _safe);}};/*创建一个日志器管理类:该类提供的作用:1、管理已创建的日志器2、可以同通过该管理器在全局任意地方根据日志器名称获取日志器3、该管理器默认提供一个标标准输出的日志器4、该管理器为单例对象全局只有一个管理器对象提供接口:getInstance();//获取日志管理器对象addLogger();//添加日志器isExists();//日志器是否存在getLogger();//获取日志器getRootLogger();//获取默认日志器管理成员;unordered_mapstring ,shared_ptrLogger _loggers;mutex保证日志管理器对象的线程安全默认日志器对象 (同步标输出)*/class loggerManager{public:static loggerManager getInstance(){static loggerManager lo;return lo;}void addLogger(std::shared_ptrLogger ptr){if (isExists(ptr-name()))return;_loggers[ptr-name()] ptr;}bool isExists(const std::string name){auto it _loggers.find(name);if (it _loggers.end())return false;return true;}std::shared_ptrLogger getLogger(const std::string name){if (isExists(name))return _loggers[name];return nullptr;}std::shared_ptrLogger getRootLogger(){return _root;}private:loggerManager(){LocalLoggerBuilder lo;lo.buildFormater();lo.buildLevelLimits();lo.buildLoggerName(root);lo.buildLoggerType();lo.buildSinkStdoutSink();_root lo.build();std::string name(root);_loggers[name] _root;}loggerManager(const loggerManager) delete;loggerManager(loggerManager) delete;std::unordered_mapstd::string, std::shared_ptrLogger _loggers;std::mutex _mux;std::shared_ptrLogger _root; // 默认日志器};// 全局日志器建造者//改日志器建造者是配合日志器管理对象来使用的// 与局部日志器建造者的唯一区别就是多了一步将构建出来的日志器添加进日志器管理对象的步骤// 省去了用户需要手动添加的过程class GobalLoggerBuilder : public Builder{public:virtual std::shared_ptrLogger build() override{std::shared_ptr std::vectorstd::shared_ptrMySpace::Sink sp(_sinks, [](std::vector std::shared_ptrMySpace::Sink* ptr)-void {ptr-clear();});std::shared_ptrLogger it(nullptr);if (_type Logger::LoggerType::LOGGER_SYNC)it std::make_sharedSyncLogger(_fmt, _sinks, _loggerName, _levelLimits);elseit std::make_sharedAsyncLogger(_fmt, _sinks, _loggerName, _levelLimits, _safe);//_sinks.clear();loggerManager::getInstance().addLogger(it);return it;}}; }整合模块 为了进一步优化用户的使用体验我们将对使用接口进行进一步封装: 封装代码: //logEncapsulation.hpp #pragma once #include iostream #include memory #include stdio.h #include unistd.h #include util.hpp #include level.hpp #include logMes.hpp #include format.hpp #include sink.hpp #include sock.hpp #include logger.hpp #include buffer.hpp // #include looper.hpp /* 为了更好的用户体验我们可以在进行一层封装 1. 封装一个全局函数专门用来获取日志器; 2. 封装一个全局函数专门用来获取默认日志器封装一层日志输出宏 预期:logger-debug(fmt,....); 也就是不用我们用户再自己输入__FILE__,__LINE__了针对默认日志器单独一套输出宏*/namespace MySpace {// 获取日志器std::shared_ptrLogger getLogger(const std::string name){return loggerManager::getInstance().getLogger(name);}// 获取默认日志器std::shared_ptrLogger getRootLogger(){return loggerManager::getInstance().getRootLogger();}// 预期使用:logger-Debug(fmt,...); 实际:logger-debug(__FILE,__LINE__,fmt,...) #define Debug(fmt, ...) debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Info(fmt, ...) info(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Warning(fmt, ...) warning(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Error(fmt, ...) error(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Fatal(fmt, ...) fatal(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Unknown(fmt, ...) unknown(__FILE__, __LINE__, fmt, ##__VA_ARGS__)// 针对默认日志器单独一套: // 只要使用LOGD(%s,测试);//就是利用标砖输出日志器输出 #define LOGD(fmt, ...) MySpace::getRootLogger()-debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOGI(fmt, ...) MySpace::getRootLogger()-info(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOGW(fmt, ...) MySpace::getRootLogger()-warning(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOGE(fmt, ...) MySpace::getRootLogger()-error(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOGF(fmt, ...) MySpace::getRootLogger()-fatal(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOGU(fmt, ...) MySpace::getRootLogger()-unknown(__FILE__, __LINE__, fmt, ##__VA_ARGS__)// 针对普通日志器也一套: // 只要使用LOG_DEBUG(sync,%s,测试...);就是使用指定日志器进行日志输出 #define LOG_DEBUG(name, fmt, ...) MySpace::getLogger(name)-debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOG_INFO(name, fmt, ...) MySpace::getLogger(name)-info(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOG_WARN(name, fmt, ...) MySpace::getLogger(name)-warning(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOG_ERROR(name, fmt, ...) MySpace::getLogger(name)-error(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOG_FATAL(name, fmt, ...) MySpace::getLogger(name)-fatal(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOG_UNKNOWN(name, fmt, ...) MySpace::getLogger(name)-unknown(__FILE__, __LINE__, fmt, ##__VA_ARGS__)} // namespace MySpace 总的头文件 //log.hpp #pragma once #include iostream #include memory #include stdio.h #ifdef __linux__ #include unistd.h #endif #include util.hpp #include level.hpp #include logMes.hpp #include format.hpp #include sink.hpp #include sock.hpp #include logger.hpp #include buffer.hpp #include logEncapsulation.hpp 日志系统使用样例:
http://wiki.neutronadmin.com/news/384420/

相关文章:

  • dnf怎么做发卡网站上海平台网站建设哪家好
  • 网站 需求为什么python不适合开发网站
  • 什么网站做生鲜比较好WordPress添加网页背景图片
  • app网站的优点dedecms 建两个网站的问题
  • 乐清网站建设做网站799元网站文件权限
  • 代码编辑器做热点什么网站好郑州微信网站
  • 个人接单做网站挣钱不东莞网站关键词
  • 南雄网站建设数据库wordpress搬家
  • 微信开放平台 网站应用开发网络营销策划书模板
  • 学习网站后台维护深圳东门老街在哪个区
  • 怎样制作网页文件windows7优化大师官方下载
  • 建设网上商城网站网站设计建设
  • 保定企业自助建站系统泸州市建设职工培训中心网站
  • 企业进行网站建设的方式有哪些网站装修的代码怎么做
  • 比较好的做网站的公司2022年企业年报网上申报流程
  • logo图案素材免费网站开发一款网站需要多少钱
  • 网站开发接口公共资源交易中心上班怎么样
  • 17做网站广州沙河地址品牌建设和品牌打造方法
  • 山东省优质高职院校建设网站我的世界服务器赞助网站怎么做
  • 张槎九江网站建设seo是什么岗位的缩写
  • 南宁专门建网站的公司免费建站系统博客
  • 英文网站建设口碑好南京网站建设 零云建站
  • 网站建设腾讯云如何制作自己的网站?
  • 橘色网站模板新媒体运营培训课程
  • 山东济南seo整站优化费用网站制作网站开发
  • 做免费嗳暧视频网站长沙做网站seo优化外包
  • 查看网站有多少空间注册网站商城需要什么条件
  • 网站开发PRD怎么看网站域名
  • 动易网站开发网站百度知道
  • 网站建设咨询云尚网络wordpress 功能介绍