Muduo源码笔记系列:
muduo源码阅读笔记(2、对C语言原生的线程安全以及同步的API的封装)
muduo源码阅读笔记(6、ExevntLoop和Thread)
muduo源码阅读笔记(7、EventLoopThreadPool)
前言
Muduo的日志设计的非常简单,日志的格式是固定的,一条日志包括:[日志头,日志体,日志尾]。实际上,参考工业级日志的使用来看,日志还应该能支持格式变更,即用户可以自定义日志的格式,选择自己关心的日志条目进行输出,或者在日志中添加一些额外的字符来修饰日志。考虑到Muduo的核心是网络库,而不是日志库,这些点就不过多深入讨论。
日志消息体输出到Impl::stream_
(简化日志的使用方式(宏定义 + 临时对象的编程技巧)
调用匿名对象的stream()
成员函数,会返回一个类型为LogStream的引用也即Impl::stream_
对象本身,muduo对LogStream
类进行了详细的>>
操作符重载,这部分代码简单易读,就不详细赘述了,这样就能将字符串类型/数值类型的数据使用>>
操作符输出到Impl::stream_
上(类似std::cout的使用)
日志消息体的输出:
1 | // |
综合Logger
以及LogStream
的实现可知,在程序运行期间,通过上面的宏使用muduo的日志时,创建的Logger临时对象会在 栈上开辟一段很大的空间(一般是detail::kSmallBuffer(4000byte)) 缓存日志
日志消息头输出到Impl::stream_
结合上面的宏定义来讲,在muduo中,当临时的Logger对象构造时,在其构造函数中,首先会自动输出一条日志的基本头部信息,比如对宏定义传来的时间戳进行格式化输出,输出线程所在的tid以及日志级别,如果是一条错误报告的log(此时errno非0),还会输出错误码的字符串信息。
1 | Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line) |
日志尾部输出到Impl::stream_
&& Impl::stream_
对象直接输出到日志输出地(LogAppender)(利用临时对象行生命周期的特点,在析构中,同步(默认)输出日志。
这里的LogAppender可能代表磁盘上的文件、控制台std::cout、数据库等。
Muduo的日志中g_output其实是类型是函数指针的全局变量,这里通过函数指针实现了C语言的多态,Muduo默认的g_output
是直接将Impl::stream_
拼接的日志输出到控制台,即输出是同步的。当然,如果用户参考g_output的定义,实现了自己的输出函数,可以通过Logger::setOutput()
接口,提供自定义函数的地址作为参数,将自定义函数安装到g_output
上。后面Muduo实现的异步日志就是这么干的。
1 | void defaultOutput(const char* msg, int len) |
日志效果
LogHeader | LogBody | LogTail |
---|---|---|
Time ThreadID LogLevel | LogMessage | - FileName:LineNumber |
1 | 20240109 03:21:56.970321Z 3094 INFO Hello - Logging_test.cc:69 |
细节明细
在 Muduo 中,为了实现日志的功能,使用了一个内部的 Impl 类来处理日志的具体实现细节。这样的设计有几个优点:
封装性:
将日志的具体实现封装在 Impl 类中,使得日志系统的使用者无需关心内部的具体实现细节。这样可以减少用户对日志系统内部的依赖,提高系统的封装性和可维护性。灵活性:
Impl 类的存在使得 Muduo 可以更加灵活地修改、扩展或者替换日志系统的具体实现,而不会对外部接口产生影响。如果未来需要更换日志库、修改日志输出格式等,只需修改 Impl 类的实现而不必修改用户代码。解耦:
通过引入 Impl 类,日志系统的实现与接口之间形成了一种解耦。这种解耦有助于降低模块之间的依赖性,提高代码的灵活性和可维护性。信息隐藏:
Impl 类将具体的实现细节隐藏在类的私有部分,只暴露必要的接口给外部。这有助于控制用户对日志系统内部的访问权限,同时防止滥用或错误的使用。
muduo还统一了日志级别的字符串长度,固定为6,不足的补空格,这样,也提升了一点点性能,毕竟积少成多。同时利用模板,在编译期确定字符串长度的操作,可以参考SourceFile类的数组引用构造的实现。
1 | template<int N> |
本章完结