【Linux】别再用printf了!自己造个C++日志库

【Linux】别再用printf了!自己造个C++日志库
在这里插入图片描述


文章目录

什么是日志(Log)?

1. 日志的定义

日志(Log)是程序运行时记录的重要信息,通常用于调试、监控和故障排查。它可以帮助开发者了解程序的运行状态,分析错误,甚至用于安全审计。

2. 常见的日志级别

级别描述
DEBUG详细信息,仅在调试时使用
INFO关键信息,如程序启动、结束等
WARNING警告信息,不影响运行,但需要注意
ERROR发生错误,可能影响功能
FATAL严重错误,程序可能崩溃

实现日志类

思路

[2024-08-0412:27:03][DEBUG][202938][main.cc][16]- hello world 

我们想实现一个日志类,是这样的,helloworld是我们手动输入的,前面的日志信息,是日志类自己做的,DEBUG是我们传进去的参数,我们来实现一下这个类。

首先日志类,我们看到要实现两种打印方式,一种是直接打印在屏幕上,一种是打印在文件当中,由于是两种模式,所以第一时间想到的肯定是虚类,我们创建一个虚类,然后内部定义一个打印方式,打印方式的函数我们定义为虚函数,我们在创建一个打印在屏幕上的类,创建一个打印在文件当中的类,两个类继承虚类,然后实现打印方式。
为了防止和系统当中的命名冲突,所以我们使用命名空间

namespace lyrics {}

输出方式的实现

虚类

// 策略模式classStrategy_Pattern{public:// 使用系统默认的析构函数virtual~Strategy_Pattern()=default;// 纯虚函数,需要基类实现virtualvoidLog_refresh_mode(const std::string &message)=0;};

打印在屏幕上

// 刷新到屏幕上classRefresh_to_screen:publicStrategy_Pattern{public:Refresh_to_screen(){int n =pthread_mutex_init(&_mutex,nullptr);}~Refresh_to_screen(){pthread_mutex_destroy(&_mutex);}voidLog_refresh_mode(const std::string &message){pthread_mutex_lock(&_mutex); std::cout << message << std::endl;pthread_mutex_unlock(&_mutex);}private: pthread_mutex_t _mutex;};

因为屏幕是公共资源,所以需要加锁。

打印在文件当中

const std::string Default_Path ="./log/";const std::string Default_Name ="Log.txt";// 刷新到文件中classRefresh_to_file:publicStrategy_Pattern{public:// 初始化函数用于初始化Refresh_to_file(const std::string &Path = Default_Path,const std::string &Name = Default_Name):_Default_Path(Path),_Default_Name(Name){pthread_mutex_init(&_mutex,nullptr);pthread_mutex_lock(&_mutex);// 需要检查文件是否创建if(std::filesystem::exists(_Default_Path))return;try{ std::filesystem::create_directories(_Default_Path);}catch(std::filesystem::filesystem_error &e){ std::cerr << e.what()<<"\n";}pthread_mutex_unlock(&_mutex);}voidLog_refresh_mode(const std::string &message){pthread_mutex_lock(&_mutex); std::string log = _Default_Path + _Default_Name; std::ofstream out(log, std::ios::app);if(!out.is_open())return; out << message << std::endl; out.close();pthread_mutex_unlock(&_mutex);}~Refresh_to_file(){pthread_mutex_destroy(&_mutex);}private: pthread_mutex_t _mutex; std::string _Default_Path;// 默认路径 std::string _Default_Name;// 默认文件名};

我们这里引入了C++17的新特性,也就是filesystem,filesystem提供了文件的一系列操作。

日志类

上面我们已经实现了打印方式,接下来我们直接封装Log类即可:

// 日志类classLog{public:private: std::shared_ptr<Strategy_Pattern> _Strategy;};

这里成员变量肯定是打印方式,构造函数用于初始化成员变量,我们的默认打印方式是打印在屏幕上:
下面实现的三个方法分别是调用打印方式。

// 默认刷新方式Log(){// 刷新到屏幕上 _Strategy = std::make_shared<Refresh_to_screen>();}// 刷新到文件当中voidRefreshToFile(){ _Strategy = std::make_shared<Refresh_to_file>();}// 刷先到屏幕上voidRefreshToScreen(){ _Strategy = std::make_shared<Refresh_to_screen>();}

我们还需要定义一个内部类,用于构建整条Log信息:

classLogMessage{public:// 初始化信息LogMessage(std::string filename,LogLevel level,int line,Log &logger):_filename(filename),_line(line),_loglevel(level),_currtime(GetCurrtime()),_pid(getpid()),_logger(logger){ std::stringstream ssbuffer; ssbuffer <<"["<< _currtime <<"] "<<"["<<LogLevelToStr(_loglevel)<<"] "<<"["<< _pid <<"] "<<"["<< _filename <<"] "<<"["<< _line <<"] - "; _loginfo = ssbuffer.str();}template<classT> LogMessage&operator<<(const T& data){ std::stringstream ss; ss << data; _loginfo += ss.str();return*this;}~LogMessage(){if(_logger._Strategy){ _logger._Strategy->Log_refresh_mode(_loginfo);}}private: std::string _filename;// 文件名 LogLevel _loglevel;// 日志等级 std::string _currtime;// 当前时间 pid_t _pid;// 进程pidint _line;// 行号 std::string _loginfo;//完整的日志信息 Log &_logger;//日志};

重载()运算符

//operator()重载----传递文件还有行号,构建临时对象返回拷贝 LogMessage operator()(LogLevel level,const std::string filename,int line){returnLogMessage(filename,level,line,*this);}

这个函数主要是为了我们调用起来比较方便。

Log整体

classLog{public:classLogMessage{public:// 初始化信息LogMessage(std::string filename,LogLevel level,int line,Log &logger):_filename(filename),_line(line),_loglevel(level),_currtime(GetCurrtime()),_pid(getpid()),_logger(logger){ std::stringstream ssbuffer; ssbuffer <<"["<< _currtime <<"] "<<"["<<LogLevelToStr(_loglevel)<<"] "<<"["<< _pid <<"] "<<"["<< _filename <<"] "<<"["<< _line <<"] - "; _loginfo = ssbuffer.str();}template<classT> LogMessage&operator<<(const T& data){ std::stringstream ss; ss << data; _loginfo += ss.str();return*this;}~LogMessage(){if(_logger._Strategy){ _logger._Strategy->Log_refresh_mode(_loginfo);}}private: std::string _filename;// 文件名 LogLevel _loglevel;// 日志等级 std::string _currtime;// 当前时间 pid_t _pid;// 进程pidint _line;// 行号 std::string _loginfo;//完整的日志信息 Log &_logger;//日志};public:// 默认刷新方式Log(){// 刷新到屏幕上 _Strategy = std::make_shared<Refresh_to_screen>();}//operator()重载----传递文件还有行号,构建临时对象返回拷贝 LogMessage operator()(LogLevel level,const std::string filename,int line){returnLogMessage(filename,level,line,*this);}// 刷新到文件当中voidRefreshToFile(){ _Strategy = std::make_shared<Refresh_to_file>();}// 刷先到屏幕上voidRefreshToScreen(){ _Strategy = std::make_shared<Refresh_to_screen>();}~Log(){}private: std::shared_ptr<Strategy_Pattern> _Strategy;}; Log logger;

接下来我们定义一个宏:

Log logger;#defineLOG(Level)logger(Level,__FILE__,__LINE__)

运行结果

在这里插入图片描述

总结

通过封装日志类,我们为应用程序引入了高效、灵活且可扩展的日志记录机制。日志类不仅能帮助我们记录程序运行过程中的关键信息,还能在发生错误时提供调试和故障排除的依据。通过合理的日志等级管理、日志输出格式和日志文件的滚动机制,日志类能够有效地优化程序的调试过程和问题追踪。

在设计日志类时,我们确保了线程安全性高效的日志写入以及灵活的配置,使得日志系统能够适应不同应用场景的需求。无论是开发阶段的调试,还是生产环境中的错误监控,日志类都能为开发者提供强大的支持。

封装日志类不仅提升了程序的可维护性和可扩展性,还能够增强团队协作时的信息共享和问题追溯的效率。通过这个日志类的封装,我们可以更好地管理应用程序中的日志数据,使系统在长时间运行时保持高效、稳定。

总的来说,日志类的封装为我们的应用程序提供了更加专业和系统的日志管理方案,不仅提升了开发效率,也提高了软件的可维护性和健壮性。

Read more

2019年信奥赛C++提高组csp-s初赛真题及答案解析(选择题6-10)

2019年信奥赛C++提高组csp-s初赛真题及答案解析(选择题6-10)

2019年信奥赛C++提高组csp-s初赛真题及答案解析(选择题6-10) 第 6 题 由数字 1,1,2,4,8,8 所组成的不同的 4位数的个数是()。 A. 104 B. 102 C. 98 D. 100 答案:B 解析:由数字 1,1,2,4,8,8 组成四位数,需考虑重复数字。分类讨论: * 四个数字各不相同:取 1,2,4,8,排列数 4!=24。 * 两对重复:取两个1和两个8,排列数 4 ! 2

By Ne0inhk
《C++ Web 自动化测试实战:常用函数全解析与场景化应用指南》

《C++ Web 自动化测试实战:常用函数全解析与场景化应用指南》

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 元素定位:自动化测试的 “精准导航” * 1.1 cssSelector:简洁高效的选择器 * 1.2 xpath:灵活强大的路径语言 * 二. 测试对象操作:定位后的 “核心动作” * 2.1 点击与提交:触发页面交互 * 2.2 文本输入与清除:模拟用户输入 * 2.3 文本与属性获取:验证测试结果 * 三. 窗口与弹窗控制:解决 “多窗口与弹窗干扰” * 3.1 窗口控制:句柄是关键 * 3.

By Ne0inhk
【C++】第二十六节—C++11(中) | 右值引用和移动语义(续集)+lambda

【C++】第二十六节—C++11(中) | 右值引用和移动语义(续集)+lambda

Hi,我是云边有个稻草人,C++领域博主与你分享专业知识(*^▽^*) 《C++》本篇文章所属专栏—持续更新中—欢迎订阅~ 目录 上节总览,详情见—>【C++】第二十五节—C++11 (上) | 详解列表初始化+右值引用和移动语义 本节总览 (4)右值引用和移动语义在传参中的提效 6. 类型分类 7. 引用折叠 8. 完美转发 四、lambda 1. lambda表达式语法 2. lambda的应用 3. 捕捉列表 4. lambda的原理 接着上节,正文开始—— (4)右值引用和移动语义在传参中的提效 * 查看STL文档我们发现C++11以后容器的push和insert系列的接口否增加的右值引用版本 * 当实参是一个左值时,容器内部继续调用拷贝构造进行拷贝,将对象拷贝到容器空间中的对象 * 当实参是一个右值,容器内部则调用移动构造,右值对象的资源到容器空间的对象上

By Ne0inhk
【算法一周目】位间流转,数字律动——洞察 C++ 位运算中的精妙与哲思

【算法一周目】位间流转,数字律动——洞察 C++ 位运算中的精妙与哲思

文章目录 * 常见位运算 * 1. 位1的个数 * 2. 比特位计数 * 3.汉明距离 * 4. 只出现一次的数字 * 5. 只出现一次的数字 III * 6. 只出现一次的数字 II * 7. 判定字符是否唯一 * 8. 丢失的数字 * 9. 两整数之和 * 10. 只出现一次的数字 II 常见位运算 1. 判断一个数的二进制表示的第x位是0还是1 * (n >> x) & 1 2. 将一个数的二进制表示的第x位修改成1 * n |= (1 << x) 3. 将一个数的二进制表示的第x位修改成0 * n &= ~ (1 << x) 4.

By Ne0inhk