C++高性能服务器框架——日志系统详解

本文详细介绍了C++中日志系统的设计与实现,探讨了日志系统的重要性,包括同步写日志可能导致的问题及解决方案,以及异步写日志的实现方式。在日志系统的设计上,涉及LogEvent、LogAppender、LogFormatter、Logger和LoggerManager等关键组件。文章还讨论了Linux环境下日志系统的类图和写入流程。

1|0日志文件系统

对文件系统进行修改时,需要进行很多操作。这些操作可能中途被打断,也就是说,这些操作不是“不可中断”(atomic)的。如果操作被打断,就可能造成文件系统出现不一致的状态。

例如:删除文件时,先要从目录树中移除文件的标示,然后收回文件占用的空间。如果在这两步之间操作被打断,文件占用的空间就无法收回。文件系统认为它是被占用的,但实际上目录树中已经找不到使用它的文件了。

在非日志文件系统中,要检查并修复类似的错误必须对整个文件系统的数据结构进行检查。这个操作可能会花费很长的时间。

为了避免这样的错误,日志文件系统分配了一个称为日志的区域来提前记录要对文件系统做的更改。在崩溃后,只要读取日志重新执行未完成的操作,文件系统就可以恢复一致。这种恢复是原子的,因为只存在几种情况:

  • 不需要重新执行:这个事务被标记为已经完成
  • 成功重新执行:根据日志,这个事务被重新执行
  • 无法重新执行:这个事务会被撤销,就如同这个事务没有发生过一样
  • 日志本身不完整:事务还没有被完全写入日志,他会被简单忽略

2|0日志系统的设计

2|1为什么需要日志

对于一些高频操作(如心跳包、定时器、界面绘制下的某些高频重复的行为),可能在少量次数下无法触发我们想要的行为,而通过断点的暂停方式,我们不得不重复操作几十次、上百次甚至更多,这样排查问题效率是非常低下的。对于这类操作,我们可以通过打印日志,将当时的程序行为上下文现场记录下来,然后从日志系统中找出某次不正常行为的上下文信息。

2|2日志系统的技术上的实现

同步写日志

所谓同步写日志,指的是在输出日志的地方,将日志即时写入到文件中去。采用这种方式,势必造成CPU等待,进而导致主线程“卡”在写文件处,进而造成界面卡帧。

但是,很多时候我们不用担心这种问题,主要有两个原因:其一,是用户感觉不到这种同步写文件造成的延迟;其二,是客户端除了UI线程外,还有其他与界面无关的工作线程,在这些线程中写文件一般不会对用户的体验产生什么影响。

多线程同步写日志出现的问题一——不同线程的日志事件时间序列错乱

产生这种问题的主要原因是由于多个线程同时写日志到同一个文件时,产生日志的时间和实际写入磁盘的时间不是一个原子操作。我们可以这样来理解,一个线程T1在t1时刻产生了日志,另一个线程T2在t2时刻也产生了一个日志(t2 > t1)。但是由于一些原因导致线程T1发生阻塞,并没有把日志即刻写入到文件中,而线程T2并没有发生阻塞,即刻就把日志信息写入到了文件中。这种情况的存在就会导致不同线程的日志事件时间序列错乱。

多线程同步写日志出现的问题二——不同线程的日志输出错乱拼接

假设线程A的日志信息为AAAAAA,线程B的日志信息为BBBBBB,线程C的日志信息为CCCCCC。那么会不会产生一种情况使得日志文件中的输出结果为AABBCCAABBCCAABBCC。

实际上,在类Unix系统上(包括Linux),同一个进程内针对同一个FIEL*的操作是线程安全的,也就是说在Linux系统上不会产生上述的情况发生。

在Windows系统上,由于对FILE*的操作并不是线程安全的,可能会发生上述情况。

这种同步日志的实现方式,一般用于低频写日志的软件系统中,所以可以认为这种多线程同时写日志到同一个文件中是可行的。

异步写日志

所谓异步写日志,就是通过一些线程同步技术将日志先暂存下来,然后再通过一个或多个专门的日志写入线程将这些缓存的日志写入到磁盘中。

实现的时候可以使用一个队列来存储其他线程产生的日志,日志线程从该队列中取出日志,然后将日志内容写入文件。

关于C/C++ Linux后端开发网络底层原理知识 点击 后端开发学习资料 获取,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux内核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK等等。免费视频学习地址:C/C++Linux服务器开发

/****************************************************************************** Module: VC-Logger Purpose: 记录程序日志。 1. 把日志信息输出到指定文件 2. 对于 GUI 程序,可以把日志信息发送到指定窗口 3. 对于Console应用程序,可以把日志信息发往标准输出 (std::cout) Desc: 1、功能: -------------------------------------------------------------------------------------- a) 把日志信息输出到指定文件 b) 每日生成一个日志文件 c) 对于 GUI 程序,可以把日志信息发送到指定窗口 d) 对于Console应用程序,可以把日志信息发往标准输出 (std::cout) e) 支持 MBCS / UNICODE,Console / GUI,win32 / x64 程序 f) 支持动态加载和静态加载日志组件 DLL g) 支持 DEBUG/TRACE/INFO/WARN/ERROR/FATAL 等多个日志级别 2、可用性: -------------------------------------------------------------------------------------- a) 简单纯净:不依赖任何程序库或框架 b) 使用接口简单,不需复杂的配置或设置工作 c) 提供 CStaticLogger 和 CDynamicLogger 包装类用于静态或动态加载以及操作日志组件,用户无需关注加载细节 d) 程序如果要记录多个日志文件只需为每个日志文件创建相应的 CStaticLogger 或 CDynamicLogger 对象 e) 只需调用 Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal() 等方法记录日志 f) 日志记录方法支持可变参数 g) 日志输出格式: 3、性能: -------------------------------------------------------------------------------------- a) 支持多线程同时发送写日志请求 b) 使用单独线程在后台日志,不影响工作线程的正常执行 c) 采用批处理方式批量记录日志 Usage: 方法一:(静态加载 Logger DLL) -------------------------------------------------------------------------------------- 0. 应用程序包含 StaticLogger.h 头文件 1. 创建 CStaticLogger 对象(通常为全局对象) 2. 调用 CStaticLogger->Init(...) 初始化日志组件 3. 使用 CStaticLogger->Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal() 等方法写日志 4. 调用 CStaticLogger->UnInit(...) 清理日志组件(CStaticLogger 对象析构时也会自动清理日志组件) 方法二:(动态加载 Logger DLL) -------------------------------------------------------------------------------------- 0. 应用程序包含 DynamicLogger.h 头文件 1. 创建 CDynamicLogger 对象(通常为全局对象) 2. 调用 CDynamicLogger->Init(...) 初始化日志组件 3. 使用 CDynamicLogger->Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal() 等方法写日志 4. 调用 CDynamicLogger->UnInit(...) 清理日志组件(CDynamicLogger 对象析构时也会自动清理日志组件) 方法三:(直接用导出函数加载 Logger DLL) -------------------------------------------------------------------------
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值