一、简介
log4cplus是一款优秀的基于C/C++的开源日志库。log4cplus具有线程安全,不用但心在多线程状态下写日志问题;使用灵活,可通过配置文件设置日志级别下输出位置,还可以在程序运行时动态设置日志输出级别,随心所欲掌控日志的输出;以及多粒度控制的特点。通过将信息划分优先级使其可以面向程序调试、运行、测试、和维护等全生命周期; 可以选择将信息输出到屏幕、文件、NT event log、甚至是远程服务器;通过指定策略对日志进行定期备份。可以满足大部分开发者对日志系统需求,功能全面。
二、安装
下载地址:http://sourceforge.net/projects/log4cplus/files/log4cplus-stable/
tar -xjf log4cplus-1.0.4.tar.bz2(解压缩),切换到解压缩路径下
安装:./configure;make;make install
这里已经安装成功,默认的路径lib库路径是/usr/local/lib/,头文件的位置:/usr/local/include/log4cplus
将/usr/local/lib下的和log4cplus相关的库都拷贝到/usr/lib,将头文件加到/etc/profile下的CPLUS_INCLUDE_PATH
三、使用
虽然功能强大,应该说log4cplus用起来还是比较复杂的,为了更好地使用它,先介绍一下它的基本要素。
Layouts :布局器,控制输出消息的格式.
Appenders :挂接器,与布局器紧密配合,将特定格式的消息输出到所挂接的设备终端
(如屏幕,文件等等)。
Logger :记录器,保存并跟踪对象日志信息变更的实体,当你需要对一个对象进行
记录时,就需要生成一个logger。
Categories :分类器,层次化(hierarchy)的结构,用于对被记录信息的分类,层次中
每一个节点维护一个logger的所有信息。
Priorities :优先权,包括TRACE, DEBUG, INFO, WARNING, ERROR, FATAL。
示例1:
#include <log4cplus/logger.h> #include <log4cplus/configurator.h> #include <log4cplus/loggingmacros.h> #include <iostream> using namespace std; int main(int argc, char **argv) { using namespace log4cplus; BasicConfigurator config; config.configure(); Logger logger = Logger::getInstance("main"); LOG4CPLUS_ERROR(logger, "Hello, error!"); LOG4CPLUS_WARN(logger, "Hello, warn!"); LOG4CPLUS_INFO(logger, "Hello, info!"); LOG4CPLUS_DEBUG(logger, "Hello, debug!"); return 0; }
这个例子是最简单的,只使用到了Logger记录器。默认输出到屏幕。输出格式如下:
示例二:
#include <log4cplus/logger.h> #include <log4cplus/consoleappender.h> #include <log4cplus/loggingmacros.h> #include <iostream> using namespace std; int main(int argc, char **argv) { using namespace log4cplus; Logger logger = Logger::getInstance("main"); SharedAppenderPtr appender(new ConsoleAppender()); std::string pattern = "%D{%m/%d/%y %H:%M:%S} %p - %m%n"; std::auto_ptr<Layout> layout(new PatternLayout(pattern)); appender->setLayout(layout); logger.addAppender(appender); LOG4CPLUS_ERROR(logger, "Hello, error!"); LOG4CPLUS_WARN(logger, "Hello, warn!"); LOG4CPLUS_INFO(logger, "Hello, info!"); LOG4CPLUS_DEBUG(logger, "Hello, debug!"); return 0; }
这个例子做了改进,我们使用到了Appender,Layout。上面说过,Layout用来控制日志的输出格式,有三种Layout:SimpleLayout、PatternLayout、TTCCLayout。Appender用来控制日志输出的地方,比如:控制台、文件等。而且Layout是依附于Appender的,所以,如果我们想要自己控制日志格式,那么必须先获取一个Appender,然后吧
/** * Set the layout for this appender. Note that some appenders have * their own (fixed) layouts or do not use one. For example, the * SocketAppender ignores the layout set here. */ virtual void setLayout(std::auto_ptr<Layout> layout);
这个Appender类的setLayout函数,也就是设置Layout的函数。它需要的参数是一个智能指针。
virtual void addAppender(SharedAppenderPtr newAppender);
这个是Logger类中设置Appender的函数,它需要的参数是一个SharedAppenderPtr类型。
所以如果我们要使用Appender和Layout,就需要构造这样的类型,并传入到对应的函数中去。
因为Layout是需要依附于Appender的,所以,这里我们使用了一个ConsoleAppender类以及PatternLayout类。
输出格式如下:
相比于示例1,这里的每一条日志前面都输出了时间。这就是使用Layout的效果,这里先详细介绍一个PatternLayout的使用方法,之后再介绍另外两种Layout。
PatternLayout:
可以看出通过填写特定格式的模式字符串"pattern",原始信息被包含到一堆有格式的信息当中了,这就使得
用户可以根据自身需要来定制显示内容。"pattern"可以包含普通字符串和预定义的标识符,其中:
(1)普通字符串,能够被直接显示的信息。
(2)预定义标识符,通过"%"与一个或多个字符共同构成预定义的标识符,能够产生出特定格式信息。
关于预定义标识符,log4cplus文档中提供了详细的格式说明,我每种都试了一下,以上述代码为例,根据不同
的pattern,各种消息格式使用情况列举如下:
(1)"%%",转义为%, 即,std::string pattern = "%%" 时输出: "%"
(2)"%c",输出logger名称,比如std::string pattern ="%c" 时输出: "test_logger.subtest",
也可以控制logger名称的显示层次,比如"%c{1}"时输出"test_logger",其中数字表示层次。
(3)"%D",显示本地时间,当std::string pattern ="%D" 时输出:"2004-10-16 18:55:45",%d显示标准时间,
所以当std::string pattern ="%d" 时输出 "2004-10-16 10:55:45" (因为我们是东8区,差8个小时啊)。
可以通过%d{...}定义更详细的显示格式,比如%d{%H:%M:%s}表示要显示小时:分钟:秒。大括号中可显示的
预定义标识符如下:
%a -- 表示礼拜几,英文缩写形式,比如"Fri" %A -- 表示礼拜几,比如"Friday" %b -- 表示几月份,英文缩写形式,比如"Oct" %B -- 表示几月份,"October" %c -- 标准的日期+时间格式,如 "Sat Oct 16 18:56:19 2004" %d -- 表示今天是这个月的几号(1-31)"16" %H -- 表示当前时刻是几时(0-23),如 "18" %I -- 表示当前时刻是几时(1-12),如 "6" %j -- 表示今天是哪一天(1-366),如 "290" %m -- 表示本月是哪一月(1-12),如 "10" %M -- 表示当前时刻是哪一分钟(0-59),如 "59" %p -- 表示现在是上午还是下午, AM or PM %q -- 表示当前时刻中毫秒部分(0-999),如 "237" %Q -- 表示当前时刻中带小数的毫秒部分(0-999.999),如 "430.732" %S -- 表示当前时刻的多少秒(0-59),如 "32" %U -- 表示本周是今年的第几个礼拜,以周日为第一天开始计算(0-53),如 "41" %w -- 表示礼拜几,(0-6, 礼拜天为0),如 "6" %W -- 表示本周是今年的第几个礼拜,以周一为第一天开始计算(0-53),如 "41" %x -- 标准的日期格式,如 "10/16/04" %X -- 标准的时间格式,如 "19:02:34" %y -- 两位数的年份(0-99),如 "04" %Y -- 四位数的年份,如 "2004" %Z -- 时区名,比如 "GMT"
(4)"%F",输出当前记录器所在的文件名称,比如std::string pattern ="%F" 时输出: "main.cpp"
(5)"%L",输出当前记录器所在的文件行号,比如std::string pattern ="%L" 时输出: "51"
(6)"%l",输出当前记录器所在的文件名称和行号,比如std::string pattern ="%L" 时输出:
"main.cpp:51"
(7)"%m",输出原始信息,比如std::string pattern ="%m" 时输出: "teststr",即上述代码中
LOG4CPLUS_DEBUG的第二个参数,这种实现机制可以确保原始信息被嵌入到带格式的信息中。
(8)"%n",换行符,没什么好解释的
(9)"%p",输出LogLevel,比如std::string pattern ="%p" 时输出: "DEBUG"
(10)"%t",输出记录器所在的线程ID,比如std::string pattern ="%t" 时输出: "1075298944"
(11)"%x",嵌套诊断上下文NDC (nested diagnostic context) 输出,从堆栈中弹出上下文信息,NDC可以用对
不同源的log信息(同时地)交叉输出进行区分,关于NDC方面的详细介绍会在下文中提到。
(12)格式对齐,比如std::string pattern ="%-10m"时表示左对齐,宽度是10,此时会输出"teststr ",当
然其它的控制字符也可以相同的方式来使用,比如"%-12d","%-5p"等等(刚接触log4cplus文档时还以为
"%-5p"整个字符串代表LogLevel呢,呵呵)。
SimpleLayout:
是一种简单格式的布局器,在输出的原始信息之前加上LogLevel和一个"-"。就是示例1的输出形式。
TTCCLayout:
是在PatternLayout基础上发展的一种缺省的带格式输出的布局器, 其格式由时间,线程ID,Logger和NDC 组
成(consists of time, thread, Logger and nested diagnostic context information, hence the name),
因而得名(怎么得名的?Logger里哪里有那个"C"的缩写啊!名字起得真够烂的,想扁人)。提供给那些想显示
典型的信息(一般情况下够用了)又懒得配置pattern的同志们。
示例三:
下面我们开始尝试将日志记录到文件中,这里需要用到文件相关的Appender类,log4cplus提供了三个这样的类:FileAppender类、RollingFileAppender类、DailyRollingFileAppender类。
FileAppender类:
实现了基本的文件操作,构造函数如下:
FileAppender(const log4cplus::tstring& filename, std::ios_base::openmode mode = std::ios_base::trunc, bool immediateFlush = true); filename : 文件名 mode : 文件类型,可选择的文件类型包括app、ate、binary、in、out、trunc,因为实际上只是对stl的一个简单包装,呵呵,这里就不多讲了。缺省是trunc,表示将先前文件删除。 immediateFlush :缓冲刷新标志,如果为true表示每向文件写一条记录就刷新一次缓存,否则直到FileAppender被关闭或文件缓存已满才更新文件,一般是要设置true的,比如你往文件写的过程中出现了错误(如程序非正常退出),即使文件没有正常关闭也可以保证程序终止时刻之前的所有记录都会被正常保存。
RollingFileAppender类:
RollingFileAppender类可以根据你预先设定的大小来决定是否转储,当超过该大小,后续log信息会另存到新
文件中,除了定义每个记录文件的大小之外,你还要确定在RollingFileAppender类对象构造时最多需要多少个
这样的记录文件(maxBackupIndex+1),当存储的文件数目超过maxBackupIndex+1时,会删除最早生成的文件,
保证整个文件数目等于maxBackupIndex+1。然后继续记录
构造函数如下:
RollingFileAppender(const log4cplus::tstring& filename, long maxFileSize = 10*1024*1024, // 10 MB int maxBackupIndex = 1, bool immediateFlush = true); filename : 文件名 maxFileSize : 文件的最大尺寸 maxBackupIndex : 最大记录文件数 immediateFlush : 缓冲刷新标志
log4cplus中隐含定义了文件的最小尺寸是200K,只有大于200K的设置才生效,<= 200k的设置都会被认为是
200K.
DailyRollingFileAppender类:
DailyRollingFileAppender类可以根据你预先设定的频度来决定是否转储,当超过该频度,后续log信息会另存
到新文件中,这里的频度包括:MONTHLY(每月)、WEEKLY(每周)、DAILY(每日)、TWICE_DAILY(每两天)、
HOURLY(每时)、MINUTELY(每分)
构造函数如下:
DailyRollingFileAppender(const log4cplus::tstring& filename, DailyRollingFileSchedule schedule = DAILY, bool immediateFlush = true, int maxBackupIndex = 10); filename : 文件名 schedule : 存储频度 immediateFlush : 缓冲刷新标志 maxBackupIndex : 最大记录文件数
这里我们就使用FileAppender来将日志写入到文件中,代码如下:
#include <log4cplus/logger.h> #include <log4cplus/fileappender.h> #include <log4cplus/loggingmacros.h> #include <iostream> using namespace std; int main(int argc, char **argv) { using namespace log4cplus; Logger logger = Logger::getInstance("main"); SharedAppenderPtr appender(new FileAppender("example3.log")); std::string pattern = "%D{%m/%d/%y %H:%M:%S} %p - %m%n"; std::auto_ptr<Layout> layout(new PatternLayout(pattern)); appender->setLayout(layout); logger.addAppender(appender); LOG4CPLUS_ERROR(logger, "Hello, error!"); LOG4CPLUS_WARN(logger, "Hello, warn!"); LOG4CPLUS_INFO(logger, "Hello, info!"); LOG4CPLUS_DEBUG(logger, "Hello, debug!"); return 0; }
相比于示例2,只是修改了一下Appender的类型。
输出结果:
四、优先级控制
日志系统的另一个基本功能就是能够让使用者能够按照自己的意愿控制什么时候,哪些信息可以输出。如果能够让用户在任意时刻设置允许输出的loglevel信息就好了。log4cplus通过LoglevelManager、LogLog、Filter三种方式实现了上述功能。
在研究LogLevelManager之前,首先介绍一下log4cplus中logger的存储机制,在log4cplus中,所有logger都通过一个层次化的结构(其实内部是hash表)来组织的,有一个Root级别的logger,可以通过一下方法获取。
Logger root = Logger::getRoot();
用户定义的logger都有一个名字与之对应,比如:
Logger test = Logger::getInstance("test");
可以定义该logger的子logger:
Logger subTest = Logger::getInstance("test.subtest");
注意:Root级别的logger只有通过getRoot()方法获取,Logger::getInstance("root")获得的是它的子对象而已。有了这些具有父子关系的logger之后可以分别设置其LogLevel,比如:
root.setLogLevel(...);
Test.setLogLevel(...);
subTest.setLogLevel(...);
LogLevelManager负责设置logger的优先级,各个logger可以通过setLogLevel设置自己的优先级,
当某个logger的LogLevel设置成NOT_SET_LOG_LEVEL时,该logger会继承父logger的优先级,另外,
如果定义了重名的多个logger, 对其中任何一个的修改都会同时改变其它logger。
参考: