Skip to content

log4cpp简介

安装log4cpp

下载压缩包 下载地址:https://sourceforge.net/projects/log4cpp/files/

安装步骤

bash
$ 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中包含了四个核心组件,这个代码需要完全理解其用法。

利用已学过的类与对象的知识对这段示例代码进行解读和推测。

cpp
// 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作为参数,格式化字符的意义如下:

cpp
%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的时候,这条日志会被过滤;

cpp
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

模仿示例代码的形式去设计定制化的日志系统

cpp
#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;
}

输出

bash
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,可以使用读取配置文件的方式

cpp
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
cpp
#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;
}