C++ 中如何设计一个高效 log 模块

蓝月亮
发布于 2020-9-2 15:37
浏览
0收藏

每个开发者编程中都会记录log信息,多数人都会使用log第三方库,log库使用起来很方便,但我们也需要了解log系统的原理,这里以glog为例进行分析。

开始
这里不会介绍glog中是如何控制INFO、ERROR等级别的输出的,其实就是一个宏控制,主要介绍google glog中一次LOG(INFO)过程中究竟发生了什么,以及为什么glog是线程安全的。

glog中的LOG(INFO)其实是一个宏定义,如下:

C++ 中如何设计一个高效 log 模块-鸿蒙开发者社区

而COMPACT_GOOGLE_LOG_INFO也是一个宏,如下:

C++ 中如何设计一个高效 log 模块-鸿蒙开发者社区这里就构造了一个google::LogMessage的临时对象,语句执行完就会自动析构,google::LogMessage的大体结构如下:

C++ 中如何设计一个高效 log 模块-鸿蒙开发者社区LogMessage的构造函数如下:

C++ 中如何设计一个高效 log 模块-鸿蒙开发者社区Init函数精简版如下:

C++ 中如何设计一个高效 log 模块-鸿蒙开发者社区

C++ 中如何设计一个高效 log 模块-鸿蒙开发者社区C++ 中如何设计一个高效 log 模块-鸿蒙开发者社区为什么HAVE_ALIGNED_STORAGE宏下面的代码可以直接构造对象呢?

是因为C++11后可以直接使用std::aligned_storage来申请对齐的内存地址。

 

LOG(INFO)<<”xxx”<<”yyy”中,glog是如何收集到xxx和yyy数据的呢?

我们上面看到LogMessage的构造函数中会构造核心的LogMessageData对象,看如下LogMessageData的结构体和构造函数中的注释

C++ 中如何设计一个高效 log 模块-鸿蒙开发者社区

C++ 中如何设计一个高效 log 模块-鸿蒙开发者社区

总结

glog通过重写std::ostream、std::stream_buf以及thread_local等技术实现了精简高效的日志输出功能,每行log语句都会创建一个LogMessage对象,通过LogMessage对象内部的stream收集需要输出的消息存到对象内部的栈内存message_text中,一行语句结束,LogMessage对象析构,析构时会把message_text中的数据写到文件和控制台里,写文件操作会每隔1000000字节或者每隔FLAGS_logbufsecs秒Flush到磁盘中。

 

如何设计一个高效的log模块?

从上述源码分析可知,glog是每次log都会执行写操作,并且写操作是等锁的,写文件本身就比较耗时,再加上等锁的时间,会阻塞当前写log的业务工作线程,所以glog在多线程中会导致应用程序性能不是特别好,所以如果能够减少阻塞工作线程的时间就可以设计出一个高效的log模块,将日志的写文件操作放在单独的线程中,参考陈硕muduo代码,log架构其实有很大改良空间。

 

muduo async log日志逻辑

muduo的异步日志是将写日志的操作放在单独的日志线程中,这里分为多个应用线程和专用的日志线程,同时有多块缓存,大概可以分为两大块缓存池,有收集日志的缓存池和专用于写日志的缓存池,收集日志的缓存池(buffer_vector)中有两块buffer,称为current_buffer和next_buffer,多个应用线程的日志都会写到current_buffer(buffer_mutex)中,当current_buffer满的时候,将current_buffer的指针存到buffer_vector中,同时current_buffer的指针指向next_buffer,这样应用线程可以继续写日志到current_buffer中,current_buffer的指针存到buffer_vector后,会通知到日志线程,这里加上锁来控制current_buffer(buffer_mutex),写日志的缓存池叫write_buffer_vector,里面也有两块缓存newBuffer1和newBuffer2,这时再将current_buffer的指针存入buffer_vector中,这时buffer_vector中有两块缓存的指针,之后将buffer_vector和write_buffer_vector交换,buffer_vector就是空,同时current_buffer指针指向newBuffer1,next_buffer指针指向newBuffer2,释放锁(buffer_mutex),这时log线程可以进行写操作,write_buffer_vector的大小为2,将里面的两块内存都写到文件中,同时newBuffer1和newBuffer2指针分别指向这两块内存,这样下次再执行交换操作时候write_buffer_vector和newBuffer1和newBuffer2都是空,一直循环执行这类操作,log一般都是写文件时候时间比较长,将数据memcpy到buffer中耗时较少,这样可以大幅减少等锁的时间,提升log的性能。

 

扩展

C++当前有两个比较好用的log第三方库easylogging++和spdlog,具体使用读者可以网上找相关资料,个人推荐spdlog,后续会对spdlog进行原理分析。

 


 
 

标签
收藏
回复
举报
回复
    相关推荐