【Linux】基于策略模式的简单日志设计

【Linux】基于策略模式的简单日志设计

📝前言:

这篇文章我们来讲讲Linux——基于策略模式的简单日志设计

🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀ZEEKLOG主页 愚润求学
🌄其他专栏:C++学习笔记C语言入门基础python入门基础C++刷题专栏

这里写目录标题

一,认识日志

的日志是记录系统和软件运行中发生事件的文件,主要作用是监控运行状态、记录异常信息,帮助快速定位问题并支持程序员进行问题修复。它是系统维护、故障排查和安全管理的重要工具。

日志格式中通常包括:时间戳、日志等级、日志内容
还可能包括:、文件名、行号、进程,线程相关id信息等

尽管复制已经有了大佬写好的现成的东西,但是本文还是采用设计模式- 略模式来进行一个简单日志的设计

格式要求:

[时间][⽇志等级][进程pid][对应⽇志的⽂件名][⾏号]- 消息内容(⽀持可变参数)

二,日志设计

1. 总体概述

我们的日志的关键设计包括以下两点:

  1. 根据不同的策略,把日志内容输出到不同的“文件”
    • 显示器文件
    • log.txt日志文件
  2. 形成一条完整的日志内容
    • 时间的获取
    • 日志等级的设计
    • 进程PID
    • 日志文件名和行号
    • 消息内容的“插入”(插入日志信息的string里),同时要支持可变参数的插入<<

2. Mylog.hpp

我们主要设计以下几个类:

  • LogStrategy策略模式基类,里面提供刷新方式SyncLog的“标准”,需要子类继承并重新给刷新方法实现多态
    • 子类1 ScreenLogStrategy :往显示器上刷新
    • 子类2 FileLogStrategy :往log文件里面刷新
  • Log日志主体,我们要实现的就是以后log << "日志内容"就能写入日志
    • 内部类LogMessage,采用RAII的设计思想,通过生命周期来控制日志内容的“写入”(构造)和“刷新”(析构)

以下是具体的代码:

#pragmaonce#include<sstream>#include<fstream>#include<iostream>#include<string>#include<thread>#include<mutex>#include<memory>#include<ctime>#include<sys/types.h>#include<unistd.h>#include<filesystem>// C++17的文件库, 需要⾼版本编译器和-std=c++17#defineFILEPATH"./log/"#defineFILENAME"log.txt"namespace tr {// 枚举类型,设置日志等级enumclassLogLevel{ DEBUG, INFO, WARNING, ERROR, FATAL }; std::string Level2String(LogLevel loglevel){switch(loglevel){// C++11后枚举类有严格的作用域,这里要指明是LogLevel::case LogLevel::DEBUG:return"DEBUG";case LogLevel::INFO:return"INFO";case LogLevel::WARNING:return"WARNING";case LogLevel::ERROR:return"ERROR";case LogLevel::FATAL:return"FATAL";default:return"UNKNOWN";}}// 策略模式// 基类classLogStrategy{public:virtual~LogStrategy()=default;virtualvoidSyncLog(std::string &message)=0;};// 策略 1: 刷新到屏幕上classScreenLogStrategy:publicLogStrategy{public:voidSyncLog(std::string &message)override{ _mutex.lock(); std::cerr << message << std::endl;// 打印到 cerr 上可以立即刷新 _mutex.unlock();}~ScreenLogStrategy(){ std::cout <<"~ScreenLogStrategy()"<<'\n';}private: std::mutex _mutex;// 用 C++ 的锁对象};// 策略 2: 刷新到日志文件中classFileLogStrategy:publicLogStrategy{public:FileLogStrategy(const std::string &logpath ="./",const std::string logname ="log.txt"):_logpath(logpath),_logname(logname){if(std::filesystem::exists(_logpath))return;try{ std::filesystem::create_directories(_logpath);// 如果抛异常抛的是:const std::exception 类型的}catch(const std::exception &e)// 用基类捕获所有异常{ std::cerr << e.what()<<'\n';}}voidSyncLog(std::string &message)override{ _mutex.lock(); std::string logfile = _logpath + _logname; std::ofstream outfile(logfile, std::ios_base::out | std::ios_base::app);// 文件不存在会创建if(!outfile.is_open())return; outfile << message <<'\n'; _mutex.unlock();}private: std::string _logpath; std::string _logname; std::mutex _mutex;};// 日志主体classLog{public:Log(){UseScreenLogStrategy();// 默认使用策略 1}~Log(){}voidUseScreenLogStrategy(){ _logstrategy = std::make_unique<ScreenLogStrategy>();}voidUseFileLogStrategy(){ _logstrategy = std::make_unique<FileLogStrategy>();}// 日志信息(内置类)// 为了后续实现 Mylog 的 operator() 重载的时候,返回临时变量// 然后利用临时变量的每行生命周期来实现 logmessage 的刷新classLogMessage{public:LogMessage(LogLevel type, std::string &filename,int line, Log& loger):_level(type),_pid(getpid()),_filename(filename),_time(GetTime()),_line(line),_loger(loger){ std::stringstream ss; ss <<"["<< _time <<"]"<<"["<<Level2String(_level)<<"]"<<"["<< _pid <<"]"<<"["<< _filename <<"]"<<"["<< _line <<"]"; _loginfo = ss.str();// 日志的左半部分}~LogMessage()// 生命周期结束,刷新日志{if(_loger._logstrategy) _loger._logstrategy->SyncLog(_loginfo);} std::string GetTime(){ time_t tm =time(nullptr);// 时间戳structtm curr;localtime_r(&tm,&curr);// 传入时间戳,会输出一个 struct tm 里面记录着时间char timebuffer[64];// 用来保存格式化后的时间信息snprintf(timebuffer,sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d", curr.tm_year +1900, curr.tm_mon, curr.tm_mday, curr.tm_hour, curr.tm_min, curr.tm_sec);return timebuffer;}// 重载流插入,为了让 Log 能支持<<// 底层是将插入的日志的右半部分信息添加到 _loginfotemplate<typenameT> LogMessage &operator<<(const T &info){ std::string ss = info; _loginfo += ss;//return*this;// 返回自己,实现多次 << }private: LogLevel _level; pid_t _pid; std::string _filename; std::string _time;int _line; std::string _loginfo;// 整条日志信息 Log &_loger;// 外部类对象,用来调用刷新};// Log 的仿函数,特意返回临时变量// 利用 RAII 的设计特点,创建一个LogMessage临时对象// 在构造的时候,准备好左半部分, 在 << 的时候准备好 右半部分,最后在该行结束时,生命周期结束,刷新日志 LogMessage operator()(LogLevel level, std::string filename,int line){returnLogMessage(level, filename, line,*this);}private: std::unique_ptr<LogStrategy> _logstrategy;}; Log logger;// 定义全局对象// 使⽤宏,可以进⾏代码插⼊,⽅便随时获取⽂件名和⾏号#defineLOG(type)logger(type,__FILE__,__LINE__)// __FILE__ 和 __LINE__ 可以自动获取文件名和行号// 提供选择使⽤何种日志策略的⽅法#defineENABLE_CONSOLE_LOG_STRATEGY() logger.UseScreenLogStrategy()#defineENABLE_FILE_LOG_STRATEGY() logger.UseFileLogStrategy()}

3. Main.cpp

测试代码

#include"Mylog.hpp"usingnamespace tr;intmain(){// ENABLE_CONSOLE_LOG_STRATEGY();ENABLE_FILE_LOG_STRATEGY();LOG(LogLevel::DEBUG)<<"hello world";LOG(LogLevel::ERROR)<<"hello world";LOG(LogLevel::FATAL)<<"hello world";LOG(LogLevel::INFO)<<"hello world";LOG(LogLevel::INFO)<<"hello world";return0;}
  • ENABLE_CONSOLE_LOG_STRATEGY():选择往屏幕刷新的策略
  • ENABLE_FILE_LOG_STRATEGY():选择往文件里面刷新

4. 运行效果

在这里插入图片描述

🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!