log4cpp
log4整体架构
使用
使用上的话参照整体架构进行理解,首先就是Category日志种类,然后不同种类可以绑定多种日志输出方式,然后这些日志输出方式可以指定需要的输出格式进行输出。
- 一个程序可以创建多套root,参考categoryTest.cpp
log4cpp日志格式化
log4cpp::PatternLayout* pLayout2 = new log4cpp::PatternLayout();
pLayout2->setConversionPattern("%d: %p %c %x: %m%n");
性能分析
同步日志方法
FileAppender
FileAppender这种日志记录方式是对每条日志直接调用write进行写入,因此对于单条日志数据量小的情况是效率较低的。
RollingFileAppender – RollingFileAppender.cpp
RollingFileAppender这种日志记录的方式,他是在FileAppender::_append()之后,会使用lseek系统调用获取当前文件大小,如果超过了文件限制,就进行rollOver()操作;
每次的leek系统调用会导致,他会比FileAppender效率更低。
改进方案 – 避免大批量的lseek:
可以增加一个当前文件大小的字段,每次更新日志都更新这个字段,从而避免lseek的重复性调用。
改进方案:
FileAppender
异步日志方案:
- 针对这个问题一种解决的思路就是对日志进行缓冲,减少write系统调用,从而提升效率,但是缓冲的标准有问题,就是如果一段时间都没有触发缓冲条件,那么已有的日志就会一直不进行写入。
- 这样的话就需要加入一个定时器方案,或者通过一个日志管理线程去管理。
RollingFileAppender
避免大批量的lseek:
可以增加一个当前文件大小的字段,每次更新日志都更新这个字段,从而避免lseek的重复性调用。
StringQueueAppender模式
这种模式就是第5个例子说用的模式,也就是异步日志方案,这种方式就是先将日志信息通过日志接口进行输出,不过他是输出在了StringQueueAppender缓冲区,然后在需要的时候(周期性、或者达到一定数量)就将缓冲区中的数据刷到文件中。
生产者消费者模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZNM8Of0-1652873142450)(./images/product_consume.png “log4cpp日志格式化”)]
上面的StringQueueAppender模式本质上就是个生产消费者模式,那么对于这个模型,有下面几个问题:
- 该如何唤醒日志落盘线程?
生产者消费者模式一般搭配mutex + notify的机制会更有效率;然后考虑到超时可以使用pthread_cond_timedwait(); - 每次发notify对性能有没有影响?
每次都发有影响,因此需要进行缓冲,缓冲多条再进行批量写入磁盘。 - 日志写入磁盘时是批量写入还是单条写入?
批量,单条没意义,还是write的系统调用没变; - 日志批量写入磁盘的时候会对日志记录线程有没有影响?
生产者消费者模型必定存在锁,那么在进行大批量磁盘写入就会引起日志写入阻塞,因此需要使用双缓冲机制(两个缓冲区,一个在进行磁盘写入的时候,另一个继续负责日志写入功能)。
双缓冲机制
两种方式触发缓冲日志写入磁盘:
- 写满一个缓冲区进行条件变量的notify;
- 通过pthread_cond_timedwait()去限制最大超时时间,避免日志写入磁盘异步太久。一般就是1秒,具体看场景。
- 如果日志写高峰,那么可以以缓冲区队列的形式去不断缓冲。甚至可以使用两个队列进行缓冲。
categoryTest测试代码:
#include "log4cpp/Category.hh"
#include "log4cpp/FileAppender.hh"
#include "log4cpp/OstreamAppender.hh"
#include "log4cpp/BasicLayout.hh"
#include "log4cpp/SimpleLayout.hh"
#include <log4cpp/PatternLayout.hh>
int main()
{
log4cpp::Layout *layout = new log4cpp::SimpleLayout(); // 就简单的格式
log4cpp::Appender *osappender1 = new log4cpp::OstreamAppender("OstreamAppender", &std::cout); // 纯粹的享受,直接控制端打印
osappender1->setLayout(layout);
log4cpp::Category &log_test1 = log4cpp::Category::getInstance("RootName.test1");
log_test1.setAdditivity(true);
log_test1.addAppender(osappender1);
log_test1.setPriority(log4cpp::Priority::DEBUG); // 设置category的优先级,低于此优先级的日志不被记录
log_test1.error("Program info which cannot be wirten, welsey = %d", 100);
//*********************************************************************************
log4cpp::PatternLayout* pLayout1 = new log4cpp::PatternLayout();
pLayout1->setConversionPattern("%d: %p %c %x: %m%n");
log4cpp::Appender* fileAppender = new log4cpp::FileAppender("rootLog","rootLog.log", true);
fileAppender->setLayout(pLayout1);
log4cpp::Category& root = log4cpp::Category::getRoot().getInstance("RootName");
root.addAppender(fileAppender);
root.setPriority(log4cpp::Priority::DEBUG);
root.info("hello");
//*********************************************************************************
log4cpp::Layout *layout2 = new log4cpp::SimpleLayout(); // 就简单的格式
log4cpp::Appender *osappender2 = new log4cpp::OstreamAppender("OstreamAppender", &std::cout); // 纯粹的享受,直接控制端打印
osappender2->setLayout(layout2);
log4cpp::Category &log_test1_2 = log4cpp::Category::getInstance("RootName.test1.test2");
log_test1_2.setAdditivity(true);
log_test1_2.setAppender(osappender2);
log_test1_2.setPriority(log4cpp::Priority::DEBUG);
log_test1_2.warn("Program info which cannot be wirten, welsey = %d", 100);
//*********************************************************************************
log4cpp::PatternLayout* pLayout3 = new log4cpp::PatternLayout();
pLayout3->setConversionPattern("%d: %p %c %x: %m%n");
log4cpp::Appender* fileAppender2 = new log4cpp::FileAppender("rootLog","rootLog2.log", true);
fileAppender2->setLayout(pLayout3);
log4cpp::Category& root2 = log4cpp::Category::getRoot().getInstance("RootName2");
root2.addAppender(fileAppender2);
root2.setPriority(log4cpp::Priority::DEBUG);
root2.info("hello");
// clean up and flush all appenders
log4cpp::Category::shutdown();
return 0;
}