本文是一篇源码导读类文章,主要是写给MongoDB的开发者看的,首先会简单介绍一下MongoDB的日志系统的组成,然后会再介绍一下如何使用。
日志系统组成
首先介绍一下日志系统的几个基本组件和概念。
Encoder
Encoder类负责对日志格式化,在不同的场景可能需要不同格式的日志,比如输出到console和文件可以使用一种格式,输出到syslog用另一种格式,还有些时候是不需要特定格式,直接输出raw信息即可。那么只要定义一个特定格式的派生类,实现对应的encode()接口即可。MongoDB目前定义了以下3种Encoder分别用于console/文件、syslog和raw:
- MessageEventDetailsEncoder
- MessageEventWithContextEncoder
- MessageEventUnadornedEncoder
Appender
和其他日志系统一样,Appender就是日志输出到什么地方,常见的有Console、File、syslog等。作为一个服务,日志切割是普遍需求,为此MongoDB实现了一个RotatableFileAppender结合RotatableFileWriter和RotatableFileManager来实现这个功能。
每个Appender类有一个append()接口,所有派生类都需要实现这个接口。Appender类在构造时需要指定一个Encoder,append()的时候调用Encoder的encode()接口对日志进行格式化,然后再输出。MongoDB目前定义了以下几种Appender分别用于console、File和syslog输出:
- ConsoleAppender
- RotatableFileAppender
- SyslogAppender
LogDomain
LogDomain可以理解为用来对日志按照业务类型进行分类(Domain)。这里业务类型是一个抽象的说法,具体可以是普通的程序日志、或是一些特殊模块的日志。MongoDB中有一个全局的LogDomain用于普通的日志输出,另外还有一个专门用于JavaScript输出。每个LogDomain管理了一组Appender,因此可以实现比较灵活的配置,比如系统日志可以输出到一个文件,JavsScript相关的日志可以输出到console或是另一个文件。除Appender的管理方法(attachAppender()、detachAppender()、clearAppenders())外,LogDomain也提供一个append()方法,调用后就是遍历其包含的Appender依次调用append()方法。
LogManager
LogDomain的管理者,包含一个全局的LogDomain以及一个LogDomain map,可以根据LogDomain的名字获取到对应的LogDomain。
LogComponent
标识日志是哪个组件产生的,是一个枚举值,有
- kDefault
- kControl
- kCommand
- ...
如果没有明确的所属组件,那么使用kDefault这个默认组件。
LogSeverity
日志级别,按照严重程度包含以下级别:
- Severe
- Error
- Warning
- Info
- Log
- Debug(1)
- Debug(2)
各级别有一个对应的整型值,从小到大依次对应-4到2。当一个log调用时使用的日志级别对应的整型值大于当前配置的日志级别的整型值时,这个log才会被记录。可以在YAML配置文件中使用systemLog.verbosity来配置日志级别,注意这个配置只能配置非负值,即0、1、2分别对应Log、Debug(1)和Debug(2)。因此,值为负数的日志,即Info级别以上的的日志是一定会被记录的。另外,MongoDB可以支持不同的LogComponent使用不同的LogSeverity,比如可以配置systemLog.component.control.verbosity等。
LogstreamBuilder
根据提供的LogDomain、的LogSeverity和LogComponent等封装一个日志流,包含一个std::ostringstream。它重载了『<<』操作符,将日志内容输入到std::ostringstream中去,因此可以直接按照std::ostringstream的使用方式去使用。在LogstreamBuilder析构时会调用LogDomain的append()方法将日志输出到Appender中。
如何使用
MongoDB启动时在GlobalLogManager这个全局初始化函数中实例化了全局的LogManager单例,其中包含了ComponentMessageLogDomain这个全局的LogDomain。因此我们只只需要在需要记log的地方构造LogstreamBuilder,再传入日志内容就可以了。
在util/log.h中定义了以下函数直接构造一个对应的LogstreamBuilder(使用全局的LogDomain和MONGO_LOG_DEFAULT_COMPONENT):
- severe()
- error()
- warning()
- log()
此外,还定义了以下一系列LOG宏来根据传入的DLEVEL值是否大于组件配置的日志级别的值来判断是否需要记录log:
- LOG
- MONGO_LOG(DLEVEL)
- MONGO_LOG_COMPONENT(DLEVEL, COMPONENT1)
- MONGO_LOG_COMPONENT2(DLEVEL, COMPONENT1, COMPONENT2)
- MONGO_LOG_COMPONENT3(DLEVEL, COMPONENT1, COMPONENT2, COMPONENT3)
这种方式可以指定DLEVEL,因此更加灵活。
综上,MongoDB日志系统的使用非常简单,只需:
- 在cpp文件中include util/log.h头文件,注意不可include多次
- 在当前cpp文件中定义一个MONGO_LOG_DEFAULT_COMPONENT宏
- 使用预定义的几个函数或LOG系列宏来进行log调用