日志系统项目(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

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi