log4cpp简介
安装log4cpp
下载压缩包 下载地址:https://sourceforge.net/projects/log4cpp/files/
安装步骤
$ tar xzvf log4cpp-1.1.4rc3.tar.gz
$ cd log4cpp
$ ./configure # 进行自动化构建,自动生成makefile
$ make
$ sudo make install # 安装 把头文件和库文件拷贝到系统路径下
默认头文件路径:/usr/local/include/log4cpp
默认lib库路径:/usr/local/lib
运行官方示例
打开log4cpp官网 Log for C++ Project (sourceforge.net)
拷贝simple example的内容,编译运行
编译指令:g++ log4cppTest.cc -llog4cpp -lpthread
可能报错:找不到动态库
解决办法参考 让系统找到动态库
核心组件
官网的simple example中包含了四个核心组件,这个代码需要完全理解其用法。
利用已学过的类与对象的知识对这段示例代码进行解读和推测。
// main.cpp
#include "log4cpp/Category.hh"
#include "log4cpp/Appender.hh"
#include "log4cpp/FileAppender.hh"
#include "log4cpp/OstreamAppender.hh"
#include "log4cpp/Layout.hh"
#include "log4cpp/BasicLayout.hh"
#include "log4cpp/Priority.hh"
int main(int argc, char** argv) {
log4cpp::Appender *appender1 = new log4cpp::OstreamAppender("console", &std::cout);
appender1->setLayout(new log4cpp::BasicLayout());
log4cpp::Appender *appender2 = new log4cpp::FileAppender("default", "program.log");
appender2->setLayout(new log4cpp::BasicLayout());
log4cpp::Category& root = log4cpp::Category::getRoot();
root.setPriority(log4cpp::Priority::WARN);
root.addAppender(appender1);
log4cpp::Category& sub1 = log4cpp::Category::getInstance(std::string("sub1"));
sub1.addAppender(appender2);
// use of functions for logging messages
root.error("root error");
root.info("root info");
sub1.error("sub1 error");
sub1.warn("sub1 warn");
// printf-style for logging variables
root.warn("%d + %d == %s ?", 1, 1, "two");
// use of streams for logging messages
root << log4cpp::Priority::ERROR << "Streamed root error";
root << log4cpp::Priority::INFO << "Streamed root info";
sub1 << log4cpp::Priority::ERROR << "Streamed sub1 error";
sub1 << log4cpp::Priority::WARN << "Streamed sub1 warn";
// or this way:
root.errorStream() << "Another streamed error";
return 0;
}
Appender日志目的地
通过log4cpp官网查看常用类的信息
我们关注这三个目的地类,点开后查看它们的构造函数
OstreamAppender C++通用输出流(如 cout)
FileAppender 写到本地文件中
RollingFileAppender 写到回卷文件中
OstreamAppender的构造函数传入两个参数:目的地名(随便写)、输出流指针
FileAppender的构造函数传入两个参数:目的地名、保存日志的文件名(后面两个参数使用默认值即可,分别表示以结尾附加的方式的保存日志,当前用户读写-其他用户只读)
RollingFileAppender稍复杂一些,如果没有回卷文件,将所有的日志信息都保存在一个文件中,那么随着系统的运行,产生越来越多的日志,本地日志文件会越变越大,若不加限制,则会大量占用存储空间。所以通常的做法是使用回卷文件,比如只给日志文件1G的空间,对于这1G的空间可以再次进行划分,比如使用10个文件存储日志信息,每一个文件最多100M。
RollingFileAppender构造函数的参数如上图,其中要注意的是回卷文件个数,如果这一位传入的参数是9,那么实际上会有10个文件保存日志。
回卷的机制是:先生成一个wd.log文件,该文件存满后接着写入日志,那么wd.log文件改名为wd.log.1,然后再创建一个wd.log文件,将日志内容写入其中,wd.log文件存满后接着写入日志,wd.log.1文件改名为wd.log.2,wd.log改名为wd.log.1,再创建一个wd.log文件,将最新的日志内容写入。以此类推,直到wd.log和wd.log.1、wd.log.2、... wd.log.9全都存满后再写入日志,wd.log.9(其中实际上保存着最早的日志内容)会被舍弃,编号在前的回卷文件一一进行改名,再创建新的wd.log文件保存最新的日志信息。
Layout日志布局
示例代码中使用的是BasicLayout,也就是默认的日志布局,这样一条日志最开始的信息就是日志产生时距离1970.1.1的秒数,不方便观察。
实际使用时可以用PatrrenLayout对象来定制化格式,类似于printf的格式化输出
使用new语句创建日志布局对象,通过指针调用setConversionPattern函数来设置日志布局
setConversionPattern函数接收一个string作为参数,格式化字符的意义如下:
%d %c [%p] %m%n
时间 模块名 优先级 消息本身 换行符
注意
当日志系统有多个日志目的地时,每一个目的地Appender都需要设置一个布局Layout(一对一关系)
Category日志记录器
创建Category对象时,可以用getRoot先创建root模块对象,对root模块对象设置优先级和目的地;
再用getInstance创建叶模块对象,叶模块对象会继承root模块对象的优先级和目的地,可以再去修改优先级、目的地
补充:如果没有创建根对象,直接使用getInstance创建叶对象,会先隐式地创建一个Root对象。
子Category可以继承父Category的信息:优先级、目的地
官网示例代码中Category对象的创建:先创建根对象,再创建叶对象
也可以一行语句创建叶对象
这里需要注意的是,例子中sub1本质上是绑定Category对象的引用,在代码中利用sub1去进行设置优先级、添加目的地、记录日志等操作;
getInstance的参数salesDepart表示的是日志信息中记录的Category名称,也就是日志来源 —— 对应了布局中的%c
所以一般在使用时这两者的名称取同一个名称,统一起来,能够更清楚地知道该条日志是来源于salesDepart这个模块
Priority日志优先级
对于 log4cpp 而言,有两个优先级需要注意,一个是日志记录器的优先级,另一个就是某一条日志的优先级。Category对象就是日志记录器,在使用时须设置好其优先级;某一行日志的优先级,就是Category对象在调用某一个日志记录函数时指定的级别,如 logger.debug("this is a debugmessage"),这一条日志的优先级就是DEBUG级别的。简言之:
日志系统有一个优先级A,日志信息有一个优先级B
只有B高于或等于A的时候,这条日志才会被输出(或保存),当B低于A的时候,这条日志会被过滤;
class LOG4CPP_EXPORT Priority {
public:
typedef enum {
EMERG = 0,
FATAL = 0,
ALERT = 100,
CRIT = 200,
ERROR = 300,
WARN = 400,
NOTICE = 500,
INFO = 600,
DEBUG = 700,
NOTSET = 800 //这个不代表可以使用的优先级
} PriorityLevel;
//......
}; //数值越小,优先级越高;数值越大,优先级越低
简单使用log4cpp
模仿示例代码的形式去设计定制化的日志系统
#include "log4cpp/Category.hh"
#include "log4cpp/Appender.hh"
#include "log4cpp/FileAppender.hh"
#include "log4cpp/OstreamAppender.hh"
#include "log4cpp/RollingFileAppender.hh"
#include "log4cpp/Layout.hh"
#include "log4cpp/BasicLayout.hh"
#include "log4cpp/Priority.hh"
#include <log4cpp/PatternLayout.hh>
int main(int argc, char **argv) {
// 1.设置日志布局
log4cpp::PatternLayout *ptn1 = new log4cpp::PatternLayout();
ptn1->setConversionPattern("%d %c [%p] %m%n");
log4cpp::PatternLayout *ptn2 = new log4cpp::PatternLayout();
ptn2->setConversionPattern("%d %c [%p] %m%n");
log4cpp::PatternLayout *ptn3 = new log4cpp::PatternLayout();
ptn3->setConversionPattern("%d %c [%p] %m%n");
// 2.创建对象输出器
log4cpp::OstreamAppender *pos = new log4cpp::OstreamAppender("console", &std::cout);
// 将输出器与布局绑定
pos->setLayout(ptn1);
log4cpp::FileAppender *fpos = new log4cpp::FileAppender("", "test-log.log");
fpos->setLayout(ptn2);
log4cpp::RollingFileAppender *rpos = new log4cpp::RollingFileAppender("rollingfile", "rollingfile.log", 5 * 1024, 9);
rpos->setLayout(ptn3);
// 3.创建日志记录器
// 引用名salesDepart是在代码中使用的,表示Category对象
// 参数中salesDepart是获取日志来源时返回的记录器的名字
// 一般让两者相同,方便理解
log4cpp::Category &salesDepart = log4cpp::Category::getInstance("salesDepart");
// 4.设置Category优先级
salesDepart.setPriority(log4cpp::Priority::ERROR);
// 5.给Category设置输出器
salesDepart.addAppender(pos);
salesDepart.addAppender(fpos);
salesDepart.addAppender(rpos);
// 6.记录日志
salesDepart.emerg("this is an emerg msg");
salesDepart.fatal("this is a fatal msg");
salesDepart.alert("this is an alert msg");
salesDepart.crit("this is a crit msg");
salesDepart.error("this is an error msg");
salesDepart.warn("this is an warn msg");
salesDepart.notice("this is an notice msg");
salesDepart.info("this is an info msg");
// 7.日志系统退出时回收资源
log4cpp::Category::shutdown();
return 0;
}
输出
2025-03-28 09:52:45,743 salesDepart [FATAL] this is an emerg msg
2025-03-28 09:52:45,743 salesDepart [FATAL] this is a fatal msg
2025-03-28 09:52:45,743 salesDepart [ALERT] this is an alert msg
2025-03-28 09:52:45,743 salesDepart [CRIT] this is a crit msg
2025-03-28 09:52:45,744 salesDepart [ERROR] this is an error msg
在设计日志系统时多次使用了new语句,这些核心组件的构造函数具体细节我们也并不清楚,但可以知道的是这个过程必然会申请资源,所以规范的写法在日志系统退出时要调用shutdown回收资源。
log4cpp读取配置文件
如果想要更灵活地使用log4cpp,可以使用读取配置文件的方式
log4cpp.rootCategory=DEBUG, rootAppender
log4cpp.category.sub1=DEBUG, A1, A2
log4cpp.category.sub1.sub2=DEBUG, A3
log4cpp.appender.rootAppender=ConsoleAppender
log4cpp.appender.rootAppender.layout=PatternLayout
log4cpp.appender.rootAppender.layout.ConversionPattern=%d [%p] %m%n
log4cpp.appender.A1=FileAppender
log4cpp.appender.A1.fileName=A1.log
log4cpp.appender.A1.layout=BasicLayout
log4cpp.appender.A2=FileAppender
log4cpp.appender.A2.threshold=WARN
log4cpp.appender.A2.fileName=A2.log
log4cpp.appender.A2.layout=PatternLayout
log4cpp.appender.A2.layout.ConversionPattern=%d [%p] %m%n
log4cpp.appender.A3=RollingFileAppender
log4cpp.appender.A3.fileName=A3.log
log4cpp.appender.A3.maxFileSize=200
log4cpp.appender.A3.maxBackupIndex=1
log4cpp.appender.A3.layout=PatternLayout
log4cpp.appender.A3.layout.ConversionPattern=%d [%p] %m%n
#include <log4cpp/Category.hh>
#include <log4cpp/PropertyConfigurator.hh>
int main(int argc, char* argv[])
{
std::string initFileName = "log4cpp.properties";
log4cpp::PropertyConfigurator::configure(initFileName);
log4cpp::Category& root = log4cpp::Category::getRoot();
log4cpp::Category& sub1 = log4cpp::Category::getInstance(std::string("sub1"));
log4cpp::Category& sub2 = log4cpp::Category::getInstance(std::string("sub1.sub2"));
root.warn("Storm is coming");
sub1.debug("Received storm warning");
sub1.info("Closing all hatches");
sub2.debug("Hiding solar panels");
sub2.error("Solar panels are blocked");
sub2.debug("Applying protective shield");
sub2.warn("Unfolding protective shield");
sub2.info("Solar panels are shielded");
sub1.info("All hatches closed");
root.info("Ready for storm.");
log4cpp::Category::shutdown();
return 0;
}