系列文章目录
《ZLToolKit源码学习笔记》(1)VS2019源码编译
《ZLToolKit源码学习笔记》(2)工具模块之日志功能分析(本文)
《ZLToolKit源码学习笔记》(3)工具模块之终端命令解析
《ZLToolKit源码学习笔记》(4)工具模块之消息广播器
《ZLToolKit源码学习笔记》(6)线程模块之整体框架概述
《ZLToolKit源码学习笔记》(7)线程模块之线程池组件:任务队列与线程组
《ZLToolKit源码学习笔记》(8)线程模块之线程负载计算器
《ZLToolKit源码学习笔记》(9)线程模块之任务执行器
《ZLToolKit源码学习笔记》(11)线程模块之工作线程池WorkThreadPool
《ZLToolKit源码学习笔记》(12)事件轮询模块之整体框架概述
《ZLToolKit源码学习笔记》(13)事件轮询模块之管道的简单封装
《ZLToolKit源码学习笔记》(14)事件轮询模块之定时器
《ZLToolKit源码学习笔记》(15)事件轮询模块之事件轮询器EventPoller
《ZLToolKit源码学习笔记》(16)网络模块之整体框架概述
《ZLToolKit源码学习笔记》(17)网络模块之基础接口封装类SockUtil
《ZLToolKit源码学习笔记》(18)网络模块之Buffer缓存
《ZLToolKit源码学习笔记》(19)网络模块之套接字封装
《ZLToolKit源码学习笔记》(20)网络模块之TcpServer
《ZLToolKit源码学习笔记》(21)网络模块之TcpClient与Session
《ZLToolKit源码学习笔记》(22)网络模块之UdpServer
文章目录
目录
前言
日志模块主要是logger.h和logger.cpp两个文件。
一、日志功能的使用
1、日志级别
作者定义了以下 五个日志级别:
typedef enum {
LTrace = 0, LDebug, LInfo, LWarn, LError
} LogLevel;
2、日志的调用方式
#define TraceL LogContextCapturer(getLogger(), LTrace, __FILE__,__FUNCTION__, __LINE__)
#define DebugL LogContextCapturer(getLogger(),LDebug, __FILE__,__FUNCTION__, __LINE__)
#define InfoL LogContextCapturer(getLogger(),LInfo, __FILE__,__FUNCTION__, __LINE__)
#define WarnL LogContextCapturer(getLogger(),LWarn,__FILE__, __FUNCTION__, __LINE__)
#define ErrorL LogContextCapturer(getLogger(),LError,__FILE__, __FUNCTION__, __LINE__)
#define WriteL(level) LogContextCapturer(getLogger(),level,__FILE__, __FUNCTION__, __LINE__)
头文件中分别提供了五个日志级别的宏定义,使用方式在test_logger.cpp中有示例。这里不再CV。
二、源码结构分析
1、类图
先整体看下日志模块的类图,主要分为五部分,LogChannel、LogWriter、LogContext、logger、logContextCapturer。下面将会对这五个模块分别分析。
2、各模块分析
2.1、LogContextCapturer:
日志上下文捕获器,调用者使用日志功能时,直接接触到的就是该类,每次打印日志时,都实例化一个该类的对象。类的内部,持有LogContext和Logger两个类的对象。LogContext对象存储用户输出的日志信息,Logger对象最终把LogContext中字符串信息输出。该类重载了<<运算符,有以下两个版本:
template<typename T> LogContextCapturer &operator<<(T &&data);
LogContextCapturer &operator<<(ostream &(*f)(ostream &));
第一个版本,模板函数,主要是将用户输入的log信息存储到LogContext对象中。如果传入的data是c++内置数据类型或者是标准库中实现了ostream重载的类型,可以直接使用,如果是用户自定义类,则需要自己在自定义类中重载以下运算符:
friend ostream& operator<<(ostream& out, const userClass& userObj );
第二个版本,很多人可能没见过这种方式。参数f是一个函数指针,说明这个重载版本需要的是一个函数,那么什么时候会用到这个版本的重载呢?
重载这个版本的目的是为了立即将当前的日志信息进行输出,作者在这里直接复用了STL中的std::endl。在<<std::endl时会执行这个重载函数。这里我们需要了解下std::endl本质上到底是什么?以下是windows下看到的endl的定义:
template <class _Elem, class _Traits>
basic_ostream<_Elem, _Traits>& __CLRCALL_OR_CDECL endl(
basic_ostream<_Elem, _Traits>& _Ostr) { // insert newline and flush stream
_Ostr.put(_Ostr.widen('\n'));
_Ostr.flush();
return _Ostr;
}
可以看到,endl就是一个函数,且函数原型实际上就是ostream& endl(ostream&);
用户在使用该类输出日志时,首先实例化一个类对象,在构造函数中,实例化了LogContext对象。当执行logContextCapturer<<str时,会把日志先存储到logContext中,最后在调用logContextCapturer<<endl或者对象析构时,通过Logger的对象将日志内容输出。
LogContextCapturer &LogContextCapturer::operator<<(ostream &(*f)(ostream &)) {
if (!_ctx) {
return *this;
}
_logger.write(_ctx);
_ctx.reset();
return *this;
}
2.2、LogContext
日志上下文,用于存储日志信息,包括日志级别、日志所在文件名、函数名、行号、用户待输出的日志信息。
该类继承了ostringstream类,用户待输出的日志信息会先存储在ostringstream中。前边说的用户自定义类型在使用日志功能时,需要重载以下函数的原因就是基于此。ostringstream的基类是ostream。
friend ostream& operator<<(ostream& out, const userClass& userObj );
2.3、Logger
该类是一个单例类,用于日志模块的配置管理。主要有以下功能:
配置日志输出通道。是终端还是文件、或者系统日志中,对应函数add、del、get;
设置写日志器。实际上就是控制日志是否要异步输出,对应函数setWriter;
设置各类型通道的日志级别。对应函数setLevel;
控制是同步还是异步写日志。如果没有设置写日志器,就是同步写,否则就是异步写。实际的写日志操作最终是在各类型日志通道中完成。
2.4、logWriter
写日志器类,该类是一个抽象类,定义了一个write纯虚函数。实际上,该类目前也只有一个派生类,即AsyncLogWriter类,作用是开启一个线程,先将日志存储在队列中,然后异步的将日志信息输出。
2.5、LogChannel
日志通道类,该类是一个抽象类,且是所有特定类型通道的基类。该类包含一个纯虚函数write,以及一个静态函数printTime,两个成员函数name和setLevel,以及一个虚函数format,实际的日志输出最终在format中,如果是终端,还会对不同级别的日志信息分颜色输出。
ConsoleChannel,输出日志到终端,使用std::cout。
SysLogChannel,输出日志到系统日志。windows、IOS、Android不支持。
FileChannelBase,输出日志到文件,使用ofstream。因为写日志到文件还涉及文件的保存天数、单文件大小、文件数量等配置项,所以输出日志到文件最终使用的是FileChannel类。