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

深入了解linux系统—— 日志

日志

在之前写代码的过程中,测试代码都是之间像显示器上输出内容;

当多线程像显示器文件输出时,由于没有做任何的防护,就有可能导致多线程输出信息混在一起,不方便观察。

而计算机中的日志记录系统和软件运行中发生事件的文件,作用就是:监控运行状态,记录异常信息,帮助快速定位问题并支持程序员进行问题修复。

是系统维护、故障排查和安全管理的重要工具。

简单来说:日志就像生活中的日记一样,记录程序运行时运行状态、异常信息等等。

对于一个合格的日志,要具有以下指标:

时间戳、日志等级、日志内容

文件名、行号、进程/线程id

最主要的就是时间戳、日志等级和日志内容。

一般来说,日志等级可以分为:

​ DEBUG:调试信息

​ INFO:正常输出

​ WARNING:告警信息

​ ERROR:错误信息(能够运行结束)

​ FATAL:错误(不能运行:打开文件失败等等)

日志有现成的解决方案,spdlogglogBoost.Log等等;

这里自定义实现一个日志(采用设计模式 - 策略模式)。

刷新策略

要自定义实现一个日志,这里首先来实现一种刷新策略;

假设现在存在一条日志信息,可以刷新到显示器文件中(显示器策略)、也可以刷新到指定文件中(文件策略)

这里,我们就可以设计一个基类:logflush,其中存在一个虚函数flush

对于一种刷新策略,就要继承基类logflush并重写flush方法实现自己的刷新策略。

    class logflush{virtual void flush(std::string massage) = 0;~logflush() = delete;};

显示器刷新策略

向显示器文件中刷新,这里直接使用std::cout即可

注意:日志可以被多线程使用,显示器文件就是临界资源,要对临界区进行加锁(这里就使用之间封装的Mutexlockgroup

    // 显示器刷新class displayflush : public logflush{void flush(std::string massage) override{lockgroup(_mutex);std::cout << massage << std::endl;}private:Mutex _mutex;};

文件刷新策略

向文件中刷新,首先要先打开这个文件,我们就要知道该文件的路径、文件名。

要打开一个文件、如果该文件不存在,调用open时可以新建;但是,如果路径不存在,我们这里调用就会出错。

所以,我们首先要做的是:判断文件路径是否存在,如果该路径不存在,就要新建。

这里可以使用std::filesystem:exists来判断一个路径是否存在(路径存在返回true,不存在返回false

使用std::filesystem::create_directories来创建一个路径。

    static std::string default_path = "./log";static std::string default_name = "log.log";const std::string gsep = "\r\n";class fileflush : public logflush{public:fileflush(const std::string &path = default_path, const std::string &name = default_name): _path(path), _name(name){if (std::filesystem::exists(path)){return;}// 路径不存在,创建try{std::filesystem::create_directories(_path);}catch (const std::filesystem::filesystem_error &e){std::cerr << e.what() << gsep;}}private:std::string _path;std::string _name;std::string _pathname;Mutex _mutex;};

然后就是重写flush方法:

在打开目标文件之前,要对文件路径和名称进行合并,生成文件绝对路径。

要打开目标文件(以追加方式打开,文件不存在就创建)(这里打开文件可以使用:std::ofstream out(_pathname, std::ios::app);C++17支持,std::ios::app表示以追加方式打开文件)

然后就是将日志信息输出到目标文件中,为了方便输出,这里定义一个结尾符:const std::string gsep = "\r\n";

最后关闭文件。

为了保证线程安全,要进行加锁

        void flush(std::string massage) override{lockgroup lgp(_mutex);_pathname = _path + (_path.back() == '/' ? ' ' : '/') + _name;std::ofstream out(_pathname, std::ios::app);if (!out.is_open()){// 打开文件失败return;}out << massage << gsep;}

日志信息

1. 构建日志

有了日志刷新策略,现在来实现日志log

要实现日志,首先就要有上述的刷新策略,这里默认使用显示器刷新策略:

    class Log{public:Log(){_log = std::make_unique<displayflush>();}void EnableDisplayFlush(){_log = std::make_unique<displayflush>();}void EnableFileFlush(){_log = std::make_unique<fileflush>();}private:std::unique_ptr<logflush> _logflush;}

有了上述刷新策略,现在来看日志信息:

[2025-8-24 22:42:35] [DEBUG] [641189] [test.cc] [12] log.txt 2015-8-24
[2025-8-24 22:42:35] [DEBUG] [641189] [test.cc] [13] hello

这里预期的日志信息如上,在一条日志中有存在时间,日志等级,进程pid,文件名,行号,信息等。

所以,我们就要实现获取时间的接口函数GetTime,以及日志等级enum class Level

这里,Level枚举类型默认输出是整型,我们想要以DEBUGINFO这样的形式输出,就需要提供一个方法根据日志等级获取相对对应字符串。

    std::string GetTime(){time_t tm = time(nullptr);struct tm curr;localtime_r(&tm, &curr);std::stringstream ss;ss << curr.tm_year + 1900 << "-"<< curr.tm_mon + 1 << "-"<< curr.tm_mday << " "<< curr.tm_hour << ":"<< curr.tm_min << ":"<< curr.tm_sec;return ss.str();}enum class Level{DEBUG,INFO,WARNING,ERROR,FATAL};std::string GetLevel(Level level){switch (level){case Level::DEBUG:return "DEBUG";case Level::INFO:return "INFO";case Level::WARNING:return "WARNING";case Level::ERROR:return "ERROR";case Level::FATAL:return "FATAL";default:return "UNKONW";}}

有了上述这些内容,现在来实现一条日志信息logmassage将其设计成Log内部类

一条日志,要具有 时间、日志等级、进程id、文件名、行号,日志信息;

        class Logmassage{public:private:std::string _time;       // 日志时间Level _level;            // 日志等级pid_t _pid;              // 进程idstd::string _filename;   // 文件名int _line;               // 行号std::string _logmassage; // 完整的日志信息Log *_log;               // log指针,方便刷新日志信息};

这里像日志等级,文件名、行号等不能自行获取的,就要通过构造函数参数传递进来;

然后根据这些信息,构建出来完整的日志信息。

构建完整的日志信息,可以使用C语言中的sprintf/snprintf来实现;

这里使用C++中的stringstream类。

        class Logmassage{public:Logmassage(const std::string &time, Level level, const std::string &filename, int line, Log *plog): _time(time), _level(level), _pid(getpid()), _filename(filename), _line(line), _log(log){std::stringstream ss;ss << '[' << _time << ']'<< '[' << GetLevel(_level) << ']' /*枚举类型,默认是整型*/<< '[' << _pid << ']'<< '[' << _filename << ']'<< '[' << _line << "] : ";_logmassage = ss.str();}private:std::string _time;       // 日志时间Level _level;            // 日志等级pid_t _pid;              // 进程idstd::string _filename;   // 文件名int _line;               // 行号std::string _logmassage; // 完整的日志信息Log *_log;               // log指针,方便刷新日志信息};

这里在Logmassage类中存在Log* _log的指针,方便进行日志信息刷新

2. 输入日志信息

上述已经完成了整个日志的框架,但是还缺少信息;

这里想要实现的使用日志的方式,就像cout <<这样使用<<来输入日志信息,所以就要实现operator<<方法;

并且,可以连续使用多个<<,在使用operator时,要将返回值设置成Logmassage&

            template <typename T>Logmassage &operator<<(const T &data){std::stringstream ss;ss << data;_logmassage += ss.str();return *this;}

最后,为了方便使用在Loamassage析构方法中,刷新该日志信息;

            ~Logmassage(){if (_log->_logflush){_log->_logflush->flush(_logmassage);}}

3. 使用日志

到现在,就已经将日志大概实现了出来;

但是,按照现在实现的日志,我们使用起来存在问题:

创建Logmassage就需要存在一个已经有的Log指针,而我们使用日志就要先创建Log对象。

所以这里就要实现一个仿函数,在调用Log()时,用来构建Logmassage对象并返回。

        Logmassage operator()(const std::string &time, Level level, const std::string &filename, int line){return Logmassage(time, level, filename, line, this);}

但是,就算实现了仿函数,我们要使用该日志时,还是非常麻烦的,需要传递什么时间,日志等级,文件名,行号,有没有更加简单粗暴的,就想要只传递时间,后面跟上<< 信息就可以使用日志的?

LOG(Level::DEBUG) << "hello log";

当然是可以实现的,时间需要调用GetTime方法;文件名和行号,我们知道宏__FILE____LINE__指的就是文件名和行号;

所以,我们就可以实现一个宏,调用是只需传递日志等级,就可以使用日志。

    Log log;
#define LOG(level) Log(GetTime(), level, __FILE__,__LINE__)

并且将log 定义成全局的,在使用时只需使用即可。


文章转载自:

http://ZQVQxsEm.dkgtr.cn
http://BomyqpUh.dkgtr.cn
http://0dgu4VpP.dkgtr.cn
http://LNm0QTKu.dkgtr.cn
http://hi8mQDyp.dkgtr.cn
http://3LywI5Wg.dkgtr.cn
http://wDJwo9VH.dkgtr.cn
http://l3ZsQz6l.dkgtr.cn
http://BeeddaM4.dkgtr.cn
http://jfer6mX7.dkgtr.cn
http://2P2sYszL.dkgtr.cn
http://H14uNXpY.dkgtr.cn
http://yqTyklC1.dkgtr.cn
http://91Fy7Hde.dkgtr.cn
http://R3vubuIf.dkgtr.cn
http://AA674ZBq.dkgtr.cn
http://8UK0Bv4P.dkgtr.cn
http://zQ0V7Xv1.dkgtr.cn
http://iT5DDySP.dkgtr.cn
http://s3RuVJxk.dkgtr.cn
http://EWVsnDRB.dkgtr.cn
http://b23kuuSY.dkgtr.cn
http://6gQCv5Ao.dkgtr.cn
http://0MeVYJ7U.dkgtr.cn
http://kbTmKZAg.dkgtr.cn
http://3X4o0pZx.dkgtr.cn
http://EIJdOz7a.dkgtr.cn
http://dOYLlgv5.dkgtr.cn
http://K2Z0YAuE.dkgtr.cn
http://lbTeT3di.dkgtr.cn
http://www.dtcms.com/a/372793.html

相关文章:

  • 3D开发工具HOOPS助力造船业数字化转型,打造更高效、更智能的船舶设计与协作!
  • 大语言模型时代文本水印技术的综述解读
  • 《WINDOWS 环境下32位汇编语言程序设计》第13章 过程控制(2)
  • 1. 统计推断-基于神经网络与Langevin扩散的自适应潜变量建模与优化
  • STM32U575RIT6 简单代码(参考模板)
  • 在新发布的AI论文中 pytorch 和tensorflow 的使用比例
  • Chapter3—单例模式
  • k8s可视化的解决方案及技术选型
  • K8s Ingress Annotations参数使用指南
  • Kubernetes(K8S)入门以及命令指南
  • 自建prometheus监控腾讯云k8s集群
  • Go 1.25在性能方面做了哪些提升?
  • Next.js数据获取入门:`getStaticProps` 与 `getServerSideProps`
  • 为什么要在出口路由器router配置NAT与默认路由
  • 如何 正确使用 nrm 工具 管理镜像源
  • http response的工作流程详细解析
  • FastDFS(分布式RPC调用和分布式文件储存)
  • 国内开源时序数据库IoTDB介绍
  • TCL电视机音乐播放器动效背景模仿
  • 深入解析:Vue与React的异步批处理更新机制
  • 基于Spring Boot的火灾报警系统的设计与实现(代码+数据库+LW)
  • Spring Boot的配置文件加载顺序和规则
  • B.30.10.05-JVM电商实战应用
  • vulhub fastjson 1.2.24 反序列化导致任意命令执行漏洞
  • [特殊字符] 跨端视频通话实战:腾讯云 TRTC + IM(React Native Web)
  • 【重学 MySQL】九十八、MySQL用户管理全指南:创建、修改、删除
  • 2025时序数据库选型,以IoTDB为主从架构基因到AI赋能来解析
  • 如何用表单快速构建一个用户反馈系统?
  • 2020/12 JLPT听力原文 问题四
  • 基于ConvFormer的双条件域自适应方法的故障诊断模型