日志系统项目(3)项目实现(日志落地类、日志器类)

2024-02-27 06:12

本文主要是介绍日志系统项目(3)项目实现(日志落地类、日志器类),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

日志落地类设计(简单工厂模式)

日志落地类主要负责落地日志消息到目的地。它主要包括以下内容:
这个类支持可扩展,其成员函数log设置为纯虚函数,当我们需要增加一个log输出目标,可以增加一个类继承自该类并重写log方法实现具体的落地日志逻辑。
目前实现了三个不同方向上的日志落地:

  • 标准输出: StdoutSink
  • 固定文件: FileSink
  • 滚动文件: RollSink
    • 滚动日志文件输出的必要性
      • 由于机器磁盘空间有限,我们不可能一直无限地向一个文件中增加数据
      • 如果一个日志文件体积太大,一方面是不好打开,另一方面是即时打开了由于包含数据巨大,也不利于查找我们需要的信息
      • 所以实际开发中会对单个日志文件的大小也会做一些控制,即当大小超过某个大小时〈如1GB),我们就重新创建一个新的日志文件来滚动写日志。对于那些过期的日志,大部分企业内部都有专门的运维人员去定时清理过期的日志,或者设置系统定时任务,定时清理过期日志。
    • 日志文件的滚动思想:
      日志文件滚动的条件有两个:文件大小和时间。我们可以选择:
      • 日志文件在大于1GB的时候会更换新的文件
      • 每天定点滚动一个日志文件

本项目基于文件大小的判断滚动生成新的文件

/* 日志落地模块的实现1. 抽象落地基类2. 派生子类(根据不同的落地方向进行派生)3. 使用工厂模式进行创建与表示分离
*/
namespace zyqlog
{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) override{// std::cout << // 无法指定大小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());}// 将日志消息写入到指定文件void log(const char *data, size_t len) override{_ofs.write(data, len);assert(_ofs.good()); // 判断写入是否出错}private:std::string _pathname;std::ofstream _ofs;};// 落地方向:滚动文件(以大小进行滚动)class RollBySizeSink : public LogSink{public:// 构造时传入文件名将操作句柄管理起来RollBySizeSink(const std::string &basename, size_t max_size): _basename(basename), _max_fsize(max_size), _cur_fsize(0), _name_count(0){std::string pathname = createNewFileName();// 1. 创建日志文件所在的目录util::File::createDirectory(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) override{if (_cur_fsize >= _max_fsize){_ofs.close(); // 关闭原来已经打开的文件std::string pathname = createNewFileName();_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 createNewFileName() // 进行大小判断,超过大小就创建新文件{// 获取系统时间,以时间来构造文件名扩展名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:// 通过基础文件名 + 扩展文件名组成一个实际的当前输出文件名std::string _basename; // ./logs/base- -> ./logs/base-20020809132332.logstd::ofstream _ofs;size_t _max_fsize; // 记录最大大小,当前文件超过这个大小就要切换文件size_t _cur_fsize; // 记录当前文件已经写入数据大小size_t _name_count;};class SinkFactory{public:// static LogSink::ptr create(int type)// {//     switch (type)//     {//     case 1://         return std::make_shared<StdoutSink>(); // 这样子编写是没有可扩展性的,只能通过修改源代码的方式进行处理//     }// }template<typename SinkType, typename ...Args> // 引入了模板参数之后还有问题就是不同落地类型的参数不同,有0个、一个、两个等等,因此还需要引入可变参数包static LogSink::ptr create(Args &&...args){return std::make_shared<SinkType>(std::forward<Args>(args)...);          }};
}

日志器类(Logger)设计(建造者模式)

日志器主要是用来和前端交互,当我们需要使用日志系统打印log的时候,只需要创建Logger对象调用该对象debug、info、warn、error、fatal等方法输出自己想打印的日志即可,支持解析可变参数列表和输出格式,即可以做到像使用printf函数—样打印日志。
当前日志系统支持同步日志&异步日志两种模式,两个不同的日志器唯一不同的地方在于他们在日志的落地方式上有所不同:
同步日志器:直接对日志消息进行输出。
异步日志器:将日志消息放入缓冲区,由异步线程进行输出。
因此日志器类在设计的时候先设计出一个Logger基类,在Logger基类的基础上,继承出SyncLogger同步日志器和AsyncLogger异步日志器。
且因为日志器模块是对前边多个模块的整合,想要创建一个日志器,需要设置日志器名称,设置日志输出等级,设置日志器类型,设置日志输出格式,设置落地方向,且落地方向有可能存在多个,整个日志器的创建过程较为复杂,为了保持良好的代码风格,编写出优雅的代码,因此日志器的创建这里采用了建造者模式来进行创建。

/*日志器模块:功能:对前面所有模块进行整合,向外提供接口完成不同等级日志的输出管理的成员:1. 格式化模块对象2. 落地模块对象3. 默认的日志器输出限制等级(大于等于限制等级的日志才能输出)4. 互斥锁(保证日志输出过程是线程安全的,不会出现交叉日志)5. 日志器名称(日志器的唯一标识,以便于查找)提供的操作:debug等级的日志输出操作(分别封装出日志消息LogMsg--各个接口日志等级不同)info等级的日志输出操作WARNING等级的日志输出操作error等级的日志输出操作fatal等级的日志输出操作实现:1. 抽象Logger基类(派生出同步日志器类 & 异步日志器类)2. 因为两种不同的日志器只有落地方式不同,因此将落地操作给抽象出来不同的日志器调用各自的落地操作进行日志落地模块关联中使用基类指针针对子类日志器对象进行日志管理和操作
*//*完成日志器模块:1. 抽象日志器基类2. 派生出不同的子类(同步日志器类 & 异步日志器类)
*/
namespace zyqlog
{class Logger{public:using ptr = std::shared_ptr<Logger>;Logger(const std::string &logger_name, LogLevel::value level, ForMatter::ptr &formatter, std::vector<LogSink::ptr> &sinks): _logger_name(logger_name), _limit_level(level), _formatter(formatter), _sinks(sinks.begin(), sinks.end()){}const std::string &name(){return _logger_name;}// 完成构造日志消息对象过程并进行格式化,得到格式化后的日志消息字符串--然后进行落地输出void debug(const std::string &file, size_t line, const std::string &fmt, ...){// 通过传入的参数构造出一个日志消息对象,得到格式化后的日志消息字符串--然后进行落地输出// 1. 判断当前日志是否达到了输出等级if (LogLevel::value::DEBUG < _limit_level) {return ;}// 2. 对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串va_list ap;va_start(ap, fmt);char *res;int ret = vasprintf(&res, fmt.c_str(), ap);if (ret == -1) {std::cout << "vsaprintf failed!!\n";return ;}va_end(ap);serialize(LogLevel::value::DEBUG, file, line, res);free(res);}void info(const std::string &file, size_t line, const std::string &fmt, ...){// 通过传入的参数构造出一个日志消息对象,得到格式化后的日志消息字符串--然后进行落地输出// 1. 判断当前日志是否达到了输出等级if (LogLevel::value::INFO < _limit_level) {return ;}// 2. 对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串va_list ap;va_start(ap, fmt);char *res;int ret = vasprintf(&res, fmt.c_str(), ap);if (ret == -1) {std::cout << "vsaprintf failed!!\n";return ;}va_end(ap);serialize(LogLevel::value::INFO, file, line, res);free(res);}void warning(const std::string &file, size_t line, const std::string &fmt, ...){// 通过传入的参数构造出一个日志消息对象,得到格式化后的日志消息字符串--然后进行落地输出// 1. 判断当前日志是否达到了输出等级if (LogLevel::value::WARNING < _limit_level) {return ;}// 2. 对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串va_list ap;va_start(ap, fmt);char *res;int ret = vasprintf(&res, fmt.c_str(), ap);if (ret == -1) {std::cout << "vsaprintf failed!!\n";return ;}va_end(ap);serialize(LogLevel::value::WARNING, file, line, res);free(res);}void error(const std::string &file, size_t line, const std::string &fmt, ...){// 通过传入的参数构造出一个日志消息对象,得到格式化后的日志消息字符串--然后进行落地输出// 1. 判断当前日志是否达到了输出等级if (LogLevel::value::ERROR < _limit_level) {return ;}// 2. 对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串va_list ap;va_start(ap, fmt);char *res;int ret = vasprintf(&res, fmt.c_str(), ap);if (ret == -1) {std::cout << "vsaprintf failed!!\n";return ;}va_end(ap);serialize(LogLevel::value::ERROR, file, line, res);free(res);}void fatal(const std::string &file, size_t line, const std::string &fmt, ...){// 通过传入的参数构造出一个日志消息对象,得到格式化后的日志消息字符串--然后进行落地输出// 1. 判断当前日志是否达到了输出等级if (LogLevel::value::FATAL < _limit_level) {return ;}// 2. 对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串va_list ap;va_start(ap, fmt);char *res;int ret = vasprintf(&res, fmt.c_str(), ap);if (ret == -1) {std::cout << "vsaprintf failed!!\n";return ;}va_end(ap);serialize(LogLevel::value::FATAL, file, line, res);free(res);}protected:void serialize(LogLevel::value level, const std::string &file, size_t line, char *str){// 3. 构造LogMsg对象LogMsg msg(level, line, file, _logger_name, str);// 4. 通过格式化工具对LogMsg进行格式化,得到格式化后的日志字符串std::stringstream ss;_formatter->format(ss, msg);// 5. 进行日志落地log(ss.str().c_str(), ss.str().size());}// 抽象接口完成实际的落地输出--不同的日志器会有不同的实际落地方式virtual void log(const char *data, size_t len) = 0;protected:std::mutex _mutex;std::string _logger_name;std::atomic<LogLevel::value> _limit_level;ForMatter::ptr _formatter;std::vector<LogSink::ptr> _sinks;};class SyncLogger : public Logger{public:SyncLogger(const std::string &logger_name, LogLevel::value level, ForMatter::ptr &formatter, std::vector<LogSink::ptr> &sinks): Logger(logger_name, level, formatter, sinks){}protected:void log(const char *data, size_t len) override{std::unique_lock<std::mutex> lock(_mutex);if (_sinks.empty()) return;for (auto &sink : _sinks){sink->log(data, len);}}};enum class LoggerType{LOGGER_SYNC,LOGGER_ASYNC,};
	/*同步日志器输出 test*/int main(){std::string logger_name = "sync_logger";zyqlog::LogLevel::value limit = zyqlog::LogLevel::value::WARNING;zyqlog::Formatter::ptr fmt(new zyqlog::Formatter("[%d{%H:%M:%S}][%c][%f:%l][%p]%T%m%n"));zyqlog::LogSink::ptr stdout_lsp = zyqlog::SinkFactory::create<zyqlog::StdoutSink>();zyqlog::LogSink::ptr file_lsp = zyqlog::SinkFactory::create<zyqlog::FileSink>("./logfile/test.log");zyqlog::LogSink::ptr roll_lsp = zyqlog::SinkFactory::create<zyqlog::RollBySizeSink>("./logfile/roll-", 1024*1024);std::vector<zyqlog::LogSink::ptr> sinks = {stdout_lsp, file_lsp, roll_lsp};// std::vector<zyqlog::LogSink::ptr> sinks = {stdout_lsp};zyqlog::Logger::ptr logger(new zyqlog::SyncLogger(logger_name, limit, fmt, sinks));// // 测试logger->debug(__FILE__, __LINE__, "%s", "测试日志");logger->info(__FILE__, __LINE__, "%s", "测试日志");logger->warning(__FILE__, __LINE__, "%s", "测试日志");logger->error(__FILE__, __LINE__, "%s", "测试日志");logger->fatal(__FILE__, __LINE__, "%s", "测试日志");size_t count = 0;size_t cursize = 0;while (cursize < 1024 * 1024 * 10){logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);cursize += 20;}}

从上面的同步日志器的测试可以得到,我们需要先构建出一个一个的零部件然后在使用日志器进行落地,这样用户使用起来会变得非常的不方便,因此这里使用建造者模式来建造日志器,不要让用户直接去构造日志器。

    /*使用建造者模式来建造日志器,而不要让用户直接去构造日志器,简化用户的使用复杂度*/// 1. 抽象一个日志器建造者类//  1. 设置日志器类型//  2. 将不同类型日志器的创建放到同一个日志器建造者类中完成    class LoggerBuilder {public:LoggerBuilder() : _logger_type(LoggerType::LOGGER_SYNC), _limit_level(LogLevel::value::DEBUG), _looper_type(AsyncType::ASYNC_SAFE){}void buildLoggerType(LoggerType type){_logger_type = type;}void buildEnableUnSafeAsync(){_looper_type = AsyncType::ASYNC_UNSAFE;}void buildLoggerName(const std::string &name){_logger_name = name;}void buildLoggerLevel(LogLevel::value level){_limit_level = level;}// 设置输出规则void buildFormatter(const std::string &pattern){_formatter = std::make_shared<ForMatter>(pattern);}template<typename SinkType, typename ...Args> // 可能会存在多个方向的落地因此使用vector来进行记录void buildSink(Args &&...args){LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);_sinks.push_back(psink);}virtual Logger::ptr build() = 0;protected:AsyncType _looper_type;LoggerType _logger_type;std::string _logger_name;LogLevel::value _limit_level;ForMatter::ptr _formatter;std::vector<LogSink::ptr> _sinks;};// 2. 派生出具体的建造者类---局部日志器的建造者 & 全局的日志器建造者 (后面添加全局单例管理器之后,将日志器添加全局管理)class LocalLoggerBuilder : public LoggerBuilder{public:Logger::ptr build() override{assert(!_logger_name.empty()); // 必须有日志器名称if (_formatter.get() == nullptr) // 用户没有传入日志器的输出格式,需要给以一个默认格式{_formatter = std::make_shared<ForMatter>();}if (_sinks.empty()) // 用户没有指定落地方式,这里有我们自己进行指定{buildSink<StdoutSink>();}if (_logger_type == LoggerType::LOGGER_ASYNC){return std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks, _looper_type);}else{return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);}}};
// 日志器建造者类test
int main()
{std::unique_ptr<zyqlog::LoggerBuilder> builder(new zyqlog::LocalLoggerBuilder());builder->buildLoggerLevel(zyqlog::LogLevel::value::WARNING); // 这里对零件的顺序没有要求,因此不需要指挥者--指挥零件按照什么样的顺序进行构造builder->buildLoggerName("sync_logger");builder->buildLoggerType(zyqlog::LoggerType::LOGGER_SYNC);builder->buildFormatter("[%d{%H:%M:%S}][%c][%f:%l][%p]%T%m%n");builder->buildSink<zyqlog::FileSink>("./logfile/test.log");builder->buildSink<zyqlog::RollBySizeSink>("./logfile/roll-", 1024*1024);zyqlog::Logger::ptr logger = builder->build();// 测试logger->debug(__FILE__, __LINE__, "%s", "测试日志");logger->info(__FILE__, __LINE__, "%s", "测试日志");logger->warning(__FILE__, __LINE__, "%s", "测试日志");logger->error(__FILE__, __LINE__, "%s", "测试日志");logger->fatal(__FILE__, __LINE__, "%s", "测试日志");size_t count = 0;size_t cursize = 0;while (cursize < 1024 * 1024 * 10){logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);cursize += 20;}
}

这篇关于日志系统项目(3)项目实现(日志落地类、日志器类)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/751440

相关文章

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

springboot项目中整合高德地图的实践

《springboot项目中整合高德地图的实践》:本文主要介绍springboot项目中整合高德地图的实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一:高德开放平台的使用二:创建数据库(我是用的是mysql)三:Springboot所需的依赖(根据你的需求再

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热