(一)log4cplus是C++编写的开源的日志系统,功能非常全面,用到自己开发的工程中会比较专业的,:),本文介绍了log4cplus基本概念,以及如何安装,配置。
### 简介 ###
log4cplus是C++编写的开源的日志系统,前身是java编写的log4j系统.受Apache Software License保护。作者是Tad E. Smith。log4cplus具有线程安全、灵活、以及多粒度控制的特点,通过将信息划分优先级使其可以面向程序调试、运行、测试、和维护等全生命周期; 你可以选择将信息输出到屏幕、文件、
NT event log、甚至是远程服务器;通过指定策略对日志进行定期备份等等。
### 下载 ###
最新的log4cplus可以从以下网址下载 http://log4cplus.sourceforge.net本文使用的版本为:1.0.2
### 安装 ###
1. linux下安装
tar xvzf log4cplus-x.x.x.tar.gz
cd log4cplus-x.x.x
./configure --prefix=/where/to/install
make
make install
这里我采用缺省安装路径:/usr/local,下文如无特别说明,均以此路径为准。
2. windows下安装
不需要安装,有一个msvc6存放包括源代码和用例在内的开发工程(for VC6 only),使用之前请先编译
"log4cplus_dll class"工程生成dll,或者编译"log4cplus_static class"工程生成lib.
### 使用前的配置 ###
1. linux下的配置
确保你的Makefile中包含 /usr/local/lib/liblog4cplus.a(静态库)或 -llog4cplus(动态库)即可,
头文件在/usr/local/include/log4cplus目录下。对于动态库,要想正常使用,还得将库安装路径加入到
LD_LIBRARY_PATH 中,我一般是这样做的:以管理员身份登录,在/etc/ld.so.conf中加入安装路径,这里
是/usr/local/lib,然后执行ldconfig使设置生效即可。
2. windows下的配置
将"log4cplus_dll class"工程或"log4cplus_static class"工程的dsp 文件插入到你的工程中,或者直接
把两个工程编译生成的库以及头文件所在目录放到你的工程的搜索路径中,如果你使用静态库,请在你的工程中
"project/setting/C++"的preprocessor definitions中加入LOG4CPLUS_STATIC。
### 构成要素介绍 ###
虽然功能强大,应该说log4cplus用起来还是比较复杂的,为了更好地使用它,先介绍一下它的基本要素。
Layouts :布局器,控制输出消息的格式.
Appenders :挂接器,与布局器紧密配合,将特定格式的消息输出到所挂接的设备终端
(如屏幕,文件等等)。
Logger :记录器,保存并跟踪对象日志信息变更的实体,当你需要对一个对象进行
记录时,就需要生成一个logger。
Categories :分类器,层次化(hierarchy)的结构,用于对被记录信息的分类,层次中
每一个节点维护一个logger的所有信息。
Priorities :优先权,包括TRACE, DEBUG, INFO, WARNING, ERROR, FATAL。
本文介绍了log4cplus基本概念,以及如何安装,配置,下一篇将通过例子介绍如何使用log4cplus。
(二)
本文介绍了使用log4cplus有六个步骤,并提供了一些例子引导你了解log4cplus的基本使用。 ### 基本使用 ###
使用log4cplus有六个基本步骤:
1. 实例化一个appender对象2. 实例化一个layout对象3. 将layout对象绑定(attach)到appender对象4. 实例化一个logger对象,调用静态函数:log4cplus::Logger::getInstance("logger_name")5. 将appender对象绑定(attach)到logger对象,如省略此步骤,标准输出(屏幕)appender对象会绑定到logger6. 设置logger的优先级,如省略此步骤,各种有限级的消息都将被记录
下面通过一些例子来了解log4cplus的基本使用。
〖例1〗
输出结果:10/14/04 09:06:24 - This is the FIRST log message... [main.cpp:31]10/14/04 09:06:25 - This is the SECOND log message... [main.cpp:33]
〖例2〗/* 简洁使用模式,appender输出到屏幕。*/#include <log4cplus logger.h=""></log4cplus>#include <log4cplus consoleappender.h=""></log4cplus><log4cplus></log4cplus>
using namespace log4cplus;using namespace log4cplus::helpers;
int main(){ /* step 1: Instantiate an appender object */ SharedAppenderPtr _append(new ConsoleAppender()); _append->setName("append test");
/* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test");
/* step 5: Attach the appender object to the logger */ _logger.addAppender(_append);
/* log activity */ LOG4CPLUS_DEBUG(_logger, "This is the FIRST log message...") sleep(1); LOG4CPLUS_WARN(_logger, "This is the SECOND log message...")
return 0;}
输出结果:DEBUG - This is the FIRST log message...WARN - This is the SECOND log message...
〖例3〗/* iostream模式,appender输出到屏幕。*/#include <log4cplus logger.h=""></log4cplus>#include <log4cplus consoleappender.h=""></log4cplus>#include <iomanip></iomanip> /* 其实这个东东还是放到log4cplus头文件中比较合适些,个人意见:) */using namespace log4cplus;
int main(){ /* step 1: Instantiate an appender object */ SharedAppenderPtr _append(new ConsoleAppender()); _append->setName("append test");
/* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test");
/* step 5: Attach the appender object to the logger */ _logger.addAppender(_append);
/* log activity */ LOG4CPLUS_TRACE(_logger, "This is" << " just a t" << "est." << std::endl) LOG4CPLUS_DEBUG(_logger, "This is a bool: " << true) LOG4CPLUS_INFO(_logger, "This is a char: " << 'x') LOG4CPLUS_WARN(_logger, "This is a int: " << 1000) LOG4CPLUS_ERROR(_logger, "This is a long(hex): " << std::hex << 100000000) LOG4CPLUS_FATAL(_logger, "This is a double: " << std::setprecision(15) << 1.2345234234)
return 0;}
输出结果:DEBUG - This is a bool: 1INFO - This is a char: xWARN - This is a int: 1000ERROR - This is a long(hex): 5f5e100FATAL - This is a double: 1.2345234234
〖例4〗/* 调试模式,通过loglog来控制输出调试、警告或错误信息,appender输出到屏幕。*/#include <iostream></iostream>#include <log4cplus loglog.h="" helpers=""></log4cplus>
using namespace log4cplus::helpers;
void printMsgs(void){ std::cout << "Entering printMsgs()..." << std::endl; LogLog::getLogLog()->debug("This is a Debug statement..."); LogLog::getLogLog()->warn("This is a Warning..."); LogLog::getLogLog()->error("This is a Error..."); std::cout << "Exiting printMsgs()..." << std::endl << std::endl;}
int main(){ /* LogLog类实现了debug, warn, error 函数用于输出调试、警告或错误信息, 同时提供了两个方法来进一步控制所输出的信息,其中:
setInternalDebugging方法用来控制是否屏蔽输出信息中的调试信息,当输入 参数为false则屏蔽,缺省设置为false。
setQuietMode方法用来控制是否屏蔽所有输出信息,当输入参数为true则屏蔽, 缺省设置为false。
LogLog::getLogLog()->setInternalDebugging(false); */
printMsgs();
std::cout << "Turning on debug..." << std::endl; LogLog::getLogLog()->setInternalDebugging(true); printMsgs();
std::cout << "Turning on quiet mode..." << std::endl; LogLog::getLogLog()->setQuietMode(true); printMsgs();
return 0;}
输出结果:Entering printMsgs()...log4cplus:WARN This is a Warning...log4cplus:ERROR This is a Error...Exiting printMsgs()...
Turning on debug...Entering printMsgs()...log4cplus: This is a Debug statement...log4cplus:WARN This is a Warning...log4cplus:ERROR This is a Error...Exiting printMsgs()...
Turning on quiet mode...Entering printMsgs()...Exiting printMsgs()...
需要指出的是,输出信息中总是包含"log4cplus:"前缀,有时候会感觉不爽,这是因为LogLog在实现时候死定了要这么写:
LogLog::LogLog() : mutex(LOG4CPLUS_MUTEX_CREATE), debugEnabled(false), quietMode(false), PREFIX( LOG4CPLUS_TEXT("log4cplus: ") ), WARN_PREFIX( LOG4CPLUS_TEXT("log4cplus:WARN ") ), ERR_PREFIX( LOG4CPLUS_TEXT("log4cplus:ERROR ") ){}
你可以把这些前缀换成自己看着爽的提示符号,然后重新编译,hihi。除非万不得已或者实在郁闷的不行,否则还是不要这样干。
〖例5〗
输出结果(Test.log文件):
DEBUG - Entering loop #0End line #DEBUG - Entering loop #1End line #DEBUG - Entering loop #2End line #DEBUG - Entering loop #3End line #DEBUG - Entering loop #4End line #
(三)
本文介绍了三种控制输出格式的布局管理器的概念和使用情况,通过掌握这些知识,可以更有效地控制log系统输出尽可能贴近你需求的信息来。
### 如何控制输出消息的格式 ###
前面已经讲过,log4cplus通过布局器(Layouts)来控制输出的格式,log4cplus提供了三种类型的Layouts,分别是SimpleLayout、PatternLayout、和TTCCLayout。其中:
1. SimpleLayout是一种简单格式的布局器,在输出的原始信息之前加上LogLevel和一个"-"。
比如以下代码片段:
... ...
/* step 1: Instantiate an appender object */ SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test");
/* step 2: Instantiate a layout object */ std::auto_ptr _layout(new log4cplus::SimpleLayout());
/* step 3: Attach the layout object to the appender */ _append->setLayout( _layout );
/* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test");
/* step 5: Attach the appender object to the logger */ _logger.addAppender(_append);
/* log activity */ LOG4CPLUS_DEBUG(_logger, "This is the simple formatted log message...") ... ... 将打印结果:DEBUG - This is the simple formatted log message...
2. PatternLayout是一种有词法分析功能的模式布局器,一提起模式就会想起正则表达式,这里的模式和正则表达式类似,但是远比后者简单,能够对预定义的标识符(称为conversion specifiers)进行解析,转换成特定格式输出。以下代码片段演示了如何使用PatternLayout:
... ...
/* step 1: Instantiate an appender object */ SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test"); /* step 2: Instantiate a layout object */ std::string pattern = "%d{%m/%d/%y %H:%M:%S} - %m [%l]%n"; std::auto_ptr _layout(new PatternLayout(pattern)); /* step 3: Attach the layout object to the appender */ _append->setLayout( _layout );
/* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test_logger.subtest");
/* step 5: Attach the appender object to the logger */ _logger.addAppender(_append);
/* log activity */ LOG4CPLUS_DEBUG(_logger, "teststr") ... ... 输出结果:10/16/04 18:51:25 - teststr [main.cpp:51]
可以看出通过填写特定格式的模式字符串"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呢,呵呵)。
3. TTCCLayout是在PatternLayout基础上发展的一种缺省的带格式输出的布局器, 其格式由时间,线程ID,Logger和NDC 组成(consists of time, thread, Logger and nested diagnostic context information, hence the name),因而得名(怎么得名的?Logger里哪里有那个"C"的缩写啊!名字起得真够烂的,想扁人)。提供给那些想显示典型的信息(一般情况下够用了)又懒得配置pattern的同志们。
TTCCLayout在构造时有机会选择显示本地时间或GMT时间,缺省是按照本地时间显示:TTCCLayout::TTCCLayout(bool use_gmtime = false)
以下代码片段演示了如何使用TTCCLayout:
... ...
/* step 1: Instantiate an appender object */ SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test");
/* step 2: Instantiate a layout object */ std::auto_ptr _layout(new TTCCLayout());
/* step 3: Attach the layout object to the appender */ _append->setLayout( _layout );
/* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test_logger");
/* step 5: Attach the appender object to the logger */ _logger.addAppender(_append);
/* log activity */ LOG4CPLUS_DEBUG(_logger, "teststr") ... ... 输出结果:10-16-04 19:08:27,501 [1075298944] DEBUG test_logger <> - teststr
当构造TTCCLayout对象时选择GMT时间格式时:
... ... /* step 2: Instantiate a layout object */ std::auto_ptr _layout(new TTCCLayout(true)); ... ... 输出结果:10-16-04 11:12:47,678 [1075298944] DEBUG test_logger <> - teststr
本文介绍了控制log信息格式的相关知识,下一部分将详细介绍log信息的几种文件操作方式。
(四)
将log信息记录到文件应该说是日志系统的一个基本功能,log4cplus在此基础上,提供了更多的功能,可以按照你预先设定的大小来决定是否转储,当超过该大小,后续log信息会另存到新文件中,依次类推;或者按照日期来决定是否转储。本文将详细介绍这些用法。
### 如何将log记录到文件 ###
我们在例5中给出了一个将log记录到文件的例子,用的是FileAppender类实现的,log4cplus提供了三个类用于文件操作,它们是FileAppender类、RollingFileAppender类、DailyRollingFileAppender类。
1. FileAppender类
实现了基本的文件操作功能,构造函数如下:
FileAppender(const log4cplus::tstring& filename, LOG4CPLUS_OPEN_MODE_TYPE mode = LOG4CPLUS_FSTREAM_NAMESPACE::ios::trunc, bool immediateFlush = true); filename : 文件名mode : 文件类型,可选择的文件类型包括app、ate、binary、in、out、trunc,因为实际上只是对 stl的一个简单包装,呵呵,这里就不多讲了。缺省是trunc,表示将先前文件删除。immediateFlush :缓冲刷新标志,如果为true表示每向文件写一条记录就刷新一次缓存,否则直到FileAppender 被关闭或文件缓存已满才更新文件,一般是要设置true的,比如你往文件写的过程中出现 了错误(如程序非正常退出),即使文件没有正常关闭也可以保证程序终止时刻之前的所有 记录都会被正常保存。
FileAppender类的使用情况请参考例5,这里不再赘述。
2. RollingFileAppender类
构造函数如下:log4cplus::RollingFileAppender::RollingFileAppender(const log4cplus::tstring& filename, long maxFileSize, int maxBackupIndex, bool immediateFlush)
filename : 文件名maxFileSize : 文件的最大尺寸maxBackupIndex : 最大记录文件数immediateFlush : 缓冲刷新标志 RollingFileAppender类可以根据你预先设定的大小来决定是否转储,当超过该大小,后续log信息会另存到新文件中,除了定义每个记录文件的大小之外,你还要确定在RollingFileAppender类对象构造时最多需要多少个这样的记录文件(maxBackupIndex+1),当存储的文件数目超过maxBackupIndex+1时,会删除最早生成的文件,保证整个文件数目等于maxBackupIndex+1。然后继续记录,比如以下代码片段:
... ... #define LOOP_COUNT 200000 SharedAppenderPtr _append(new RollingFileAppender("Test.log", 5*1024, 5)); _append->setName("file test"); _append->setLayout( std::auto_ptr<layout></layout>(new TTCCLayout()) ); Logger::getRoot().addAppender(_append);
Logger root = Logger::getRoot(); Logger test = Logger::getInstance("test"); Logger subTest = Logger::getInstance("test.subtest");
for(int i=0; i { NDCContextCreator _context("loop"); LOG4CPLUS_DEBUG(subTest, "Entering loop #" << i) } ... ...
运行结果:
运行后会产生6个输出文件,Test.log、Test.log.1、Test.log.2、Test.log.3、Test.log.4、Test.log.5其中Test.log存放着最新写入的信息,而最后一个文件中并不包含第一个写入信息,说明已经被不断更新了。需要指出的是,这里除了Test.log之外,每个文件的大小都是200K,而不是我们想像中的5K,这是因为log4cplus中隐含定义了文件的最小尺寸是200K,只有大于200K的设置才生效,<= 200k的设置都会被认为是200K.
3. DailyRollingFileAppender类
构造函数如下:DailyRollingFileAppender::DailyRollingFileAppender(const log4cplus::tstring& filename, DailyRollingFileSchedule schedule, bool immediateFlush, int maxBackupIndex) filename : 文件名schedule : 存储频度immediateFlush : 缓冲刷新标志maxBackupIndex : 最大记录文件数
DailyRollingFileAppender类可以根据你预先设定的频度来决定是否转储,当超过该频度,后续log信息会另存到新文件中,这里的频度包括:MONTHLY(每月)、WEEKLY(每周)、DAILY(每日)、TWICE_DAILY(每两天)、HOURLY(每时)、MINUTELY(每分)。maxBackupIndex的含义同上所述,比如以下代码片段:
... ... SharedAppenderPtr _append(new DailyRollingFileAppender("Test.log", MINUTELY, true, 5)); _append->setName("file test"); _append->setLayout( std::auto_ptr<layout></layout>(new TTCCLayout()) ); Logger::getRoot().addAppender(_append);
Logger root = Logger::getRoot(); Logger test = Logger::getInstance("test"); Logger subTest = Logger::getInstance("test.subtest");
for(int i=0; i { NDCContextCreator _context("loop"); LOG4CPLUS_DEBUG(subTest, "Entering loop #" << i) } ... ...
运行结果:
运行后会以分钟为单位,分别生成名为Test.log.2004-10-17-03-03、Test.log.2004-10-17-03-04和Test.log.2004-10-17-03-05这样的文件。
需要指出的是,刚看到按照频度(如HOURLY、MINUTELY)转储这样的概念,以为log4cplus提供了内部定时器,感觉很奇怪,因为日志系统不应该主动记录,而loging事件总是应该被动触发的啊。仔细看了源代码后才知道这里的"频度"并不是你写入文件的速度,其实是否转储的标准并不依赖你写入文件的速度,而是依赖于写入的那一时刻是否满足了频度条件,即是否超过了以分钟、小时、周、月为单位的时间刻度,如果超过了就另存。
本部分详细介绍log信息的几种文件操作方式,下面将重点介绍一下如何有选择地控制log信息的输出。
(五)
日志系统的另一个基本功能就是能够让使用者按照自己的意愿来控制什么时候,哪些log信息可以输出。如果能够让用户在任意时刻设置允许输出的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( ... );
logger的这种父子关联性会体现在优先级控制方面,log4cplus将输出的log信息按照LogLevel(从低到高)分为:
NOT_SET_LOG_LEVEL ( -1) :接受缺省的LogLevel,如果有父logger则继承它的LogLevelALL_LOG_LEVEL ( 0) :开放所有log信息输出TRACE_LOG_LEVEL ( 0) :开放trace信息输出(即ALL_LOG_LEVEL)DEBUG_LOG_LEVEL (10000) :开放debug信息输出INFO_LOG_LEVEL (20000) :开放info信息输出WARN_LOG_LEVEL (30000) :开放warning信息输出ERROR_LOG_LEVEL (40000) :开放error信息输出FATAL_LOG_LEVEL (50000) :开放fatal信息输出OFF_LOG_LEVEL (60000) :关闭所有log信息输出
LogLevelManager负责设置logger的优先级,各个logger可以通过setLogLevel设置自己的优先级,当某个logger的LogLevel设置成NOT_SET_LOG_LEVEL时,该logger会继承父logger的优先级,另外,如果定义了重名的多个logger, 对其中任何一个的修改都会同时改变其它logger,我们举例说明:
〖例6〗
#include "log4cplus/logger.h"#include "log4cplus/consoleappender.h"#include "log4cplus/loglevel.h"#include <iostream></iostream><iostream></iostream>
using namespace std;using namespace log4cplus;
int main(){ SharedAppenderPtr _append(new ConsoleAppender()); _append->setName("test"); Logger::getRoot().addAppender(_append); Logger root = Logger::getRoot();
Logger test = Logger::getInstance("test"); Logger subTest = Logger::getInstance("test.subtest"); LogLevelManager& llm = getLogLevelManager();
cout << endl << "Before Setting, Default LogLevel" << endl; LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))
cout << endl << "Setting test.subtest to WARN" << endl; subTest.setLogLevel(WARN_LOG_LEVEL); LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))
cout << endl << "Setting test.subtest to TRACE" << endl; test.setLogLevel(TRACE_LOG_LEVEL); LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))
cout << endl << "Setting test.subtest to NO_LEVEL" << endl; subTest.setLogLevel(NOT_SET_LOG_LEVEL); LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()) << '\n')
cout << "create a logger test_bak, named \"test_\", too. " << endl; Logger test_bak = Logger::getInstance("test"); cout << "Setting test to INFO, so test_bak also be set to INFO" << endl; test.setLogLevel(INFO_LOG_LEVEL); LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test_bak: " << llm.toString(test_bak.getChainedLogLevel()))
return 0;}
输出结果:
Before Setting, Default LogLevelFATAL - root: DEBUGFATAL - test: DEBUGFATAL - test.subtest: DEBUG
Setting test.subtest to WARNFATAL - root: DEBUGFATAL - test: DEBUGFATAL - test.subtest: WARN
Setting test.subtest to TRACEFATAL - root: DEBUGFATAL - test: TRACEFATAL - test.subtest: WARN
Setting test.subtest to NO_LEVELFATAL - root: DEBUGFATAL - test: TRACEFATAL - test.subtest: TRACE
create a logger test_bak, named "test_", too.Setting test to INFO, so test_bak also be set to INFOFATAL - test: INFOFATAL - test_bak: INFO
下面的例子演示了如何通过设置LogLevel来控制用户的log信息输出:
〖例7〗
#include "log4cplus/logger.h"#include "log4cplus/consoleappender.h"#include "log4cplus/loglevel.h"#include <iostream></iostream><iostream></iostream>
using namespace std;using namespace log4cplus;
void ShowMsg(void){ LOG4CPLUS_TRACE(Logger::getRoot(),"info") LOG4CPLUS_DEBUG(Logger::getRoot(),"info") LOG4CPLUS_INFO(Logger::getRoot(),"info") LOG4CPLUS_WARN(Logger::getRoot(),"info") LOG4CPLUS_ERROR(Logger::getRoot(),"info") LOG4CPLUS_FATAL(Logger::getRoot(),"info")}
int main(){ SharedAppenderPtr _append(new ConsoleAppender()); _append->setName("test"); _append->setLayout(std::auto_ptr<layout></layout>(new TTCCLayout())); Logger root = Logger::getRoot(); root.addAppender(_append);
cout << endl << "all-log allowed" << endl; root.setLogLevel(ALL_LOG_LEVEL); ShowMsg();
cout << endl << "trace-log and above allowed" << endl; root.setLogLevel(TRACE_LOG_LEVEL); ShowMsg();
cout << endl << "debug-log and above allowed" << endl; root.setLogLevel(DEBUG_LOG_LEVEL); ShowMsg();
cout << endl << "info-log and above allowed" << endl; root.setLogLevel(INFO_LOG_LEVEL); ShowMsg();
cout << endl << "warn-log and above allowed" << endl; root.setLogLevel(WARN_LOG_LEVEL); ShowMsg();
cout << endl << "error-log and above allowed" << endl; root.setLogLevel(ERROR_LOG_LEVEL); ShowMsg();
cout << endl << "fatal-log and above allowed" << endl; root.setLogLevel(FATAL_LOG_LEVEL); ShowMsg();
cout << endl << "log disabled" << endl; root.setLogLevel(OFF_LOG_LEVEL); ShowMsg();
return 0;}
输出结果:
all-log allowed10-17-04 10:11:40,587 [1075298944] TRACE root <> - info10-17-04 10:11:40,590 [1075298944] DEBUG root <> - info10-17-04 10:11:40,591 [1075298944] INFO root <> - info10-17-04 10:11:40,591 [1075298944] WARN root <> - info10-17-04 10:11:40,592 [1075298944] ERROR root <> - info10-17-04 10:11:40,592 [1075298944] FATAL root <> - info
trace-log and above allowed10-17-04 10:11:40,593 [1075298944] TRACE root <> - info10-17-04 10:11:40,593 [1075298944] DEBUG root <> - info10-17-04 10:11:40,594 [1075298944] INFO root <> - info10-17-04 10:11:40,594 [1075298944] WARN root <> - info10-17-04 10:11:40,594 [1075298944] ERROR root <> - info10-17-04 10:11:40,594 [1075298944] FATAL root <> - info
debug-log and above allowed10-17-04 10:11:40,595 [1075298944] DEBUG root <> - info10-17-04 10:11:40,595 [1075298944] INFO root <> - info10-17-04 10:11:40,596 [1075298944] WARN root <> - info10-17-04 10:11:40,596 [1075298944] ERROR root <> - info10-17-04 10:11:40,596 [1075298944] FATAL root <> - info
info-log and above allowed10-17-04 10:11:40,597 [1075298944] INFO root <> - info10-17-04 10:11:40,597 [1075298944] WARN root <> - info10-17-04 10:11:40,597 [1075298944] ERROR root <> - info10-17-04 10:11:40,598 [1075298944] FATAL root <> - info
warn-log and above allowed10-17-04 10:11:40,598 [1075298944] WARN root <> - info10-17-04 10:11:40,598 [1075298944] ERROR root <> - info10-17-04 10:11:40,599 [1075298944] FATAL root <> - info
error-log and above allowed10-17-04 10:11:40,599 [1075298944] ERROR root <> - info10-17-04 10:11:40,600 [1075298944] FATAL root <> - info
fatal-log and above allowed10-17-04 10:11:40,600 [1075298944] FATAL root <> - info
log disabled
用户也可以自行定义LogLevel,操作比较简单,首先要定义LEVEL值,比如HELLO_LOG_LEVEL定义如下:
/* DEBUG_LOG_LEVEL < HELLO_LOG_LEVEL < INFO_LOG_LEVEL */const LogLevel HELLO_LOG_LEVEL = 15000;
然后定义以下宏即可:
/* define MACRO LOG4CPLUS_HELLO */#define LOG4CPLUS_HELLO(logger, logEvent) \ if(logger.isEnabledFor(HELLO_LOG_LEVEL)) { \ log4cplus::tostringstream _log4cplus_buf; \ _log4cplus_buf << logEvent; \ logger.forcedLog(HELLO_LOG_LEVEL, _log4cplus_buf.str(), __FILE__, __LINE__); \ }
不过log4cplus没有提供给用户一个接口来实现LEVEL值与字符串的转换,所以当带格式输出LogLevel字符串时候会显示"UNKNOWN", 不够理想。比如用TTCCLayout控制输出的结果可能会如下所示:
10-17-04 11:17:51,124 [1075298944] UNKNOWN root <> - info
而不是期望的以下结果:10-17-04 11:17:51,124 [1075298944] HELLO root <> - info
要想实现第二种结果,按照log4cplus现有的接口机制,只能改其源代码后重新编译,方法是在loglevel.cxx中加入:
#define _HELLO_STRING LOG4CPLUS_TEXT("HELLO")
然后修改log4cplus::tstring defaultLogLevelToStringMethod(LogLevel ll)函数,增加一个判断:
case HELLO_LOG_LEVEL: return _HELLO_STRING;
重新编译log4cplus源代码后生成库文件,再使用时即可实现满意效果。
### 调试模式 ###
即通过loglog来控制输出调试、警告或错误信息,见例4,这里不再赘述。
### 基于脚本配置来过滤log信息 ###
除了通过程序实现对log环境的配置之外,log4cplus通过PropertyConfigurator类实现了基于脚本配置的功能。通过脚本可以完成对logger、appender和layout的配置,因此可以解决怎样输出,输出到哪里的问题,我将在全文的最后一部分中提到多线程环境中如何利用脚本配置来配合实现性能测试,本节将重点介绍基脚本实现过滤log信息的功能。
首先简单介绍一下脚本的语法规则:
包括Appender的配置语法和logger的配置语法,其中:
1.Appender的配置语法:
(1)设置名称:
/*设置方法*/log4cplus.appender.appenderName=fully.qualified.name.of.appender.class
例如(列举了所有可能的Appender,其中SocketAppender后面会讲到):log4cplus.appender.append_1=log4cplus::ConsoleAppenderlog4cplus.appender.append_2=log4cplus::FileAppenderlog4cplus.appender.append_3=log4cplus::RollingFileAppenderlog4cplus.appender.append_4=log4cplus::DailyRollingFileAppenderlog4cplus.appender.append_4=log4cplus::SocketAppender
(2)设置Filter:
包括选择过滤器和设置过滤条件,可选择的过滤器包括:LogLevelMatchFilter、LogLevelRangeFilter、和StringMatchFilter:
对LogLevelMatchFilter来说,过滤条件包括LogLevelToMatch和AcceptOnMatch(true|false), 只有当log信息的LogLevel值与LogLevelToMatch相同,且AcceptOnMatch为true时才会匹配。
LogLevelRangeFilter来说,过滤条件包括LogLevelMin、LogLevelMax和AcceptOnMatch,只有当log信息的LogLevel在LogLevelMin、LogLevelMax之间同时AcceptOnMatch为true时才会匹配。
对StringMatchFilter来说,过滤条件包括StringToMatch和AcceptOnMatch,只有当log信息的LogLevel值与StringToMatch对应的LogLevel值与相同, 且AcceptOnMatch为true时会匹配。
过滤条件处理机制类似于IPTABLE的Responsibility chain,(即先deny、再allow)不过执行顺序刚好相反,后写的条件会被先执行,比如:
log4cplus.appender.append_1.filters.1=log4cplus::spi::LogLevelMatchFilterlog4cplus.appender.append_1.filters.1.LogLevelToMatch=TRACElog4cplus.appender.append_1.filters.1.AcceptOnMatch=true#log4cplus.appender.append_1.filters.2=log4cplus::spi::DenyAllFilter
会首先执行filters.2的过滤条件,关闭所有过滤器,然后执行filters.1,仅匹配TRACE信息。
(3)设置Layout
可以选择不设置、TTCCLayout、或PatternLayout
如果不设置,会输出简单格式的log信息。
设置TTCCLayout如下所示:log4cplus.appender.ALL_MSGS.layout=log4cplus::TTCCLayout
设置PatternLayout如下所示:log4cplus.appender.append_1.layout=log4cplus::PatternLayoutlog4cplus.appender.append_1.layout.ConversionPattern=%d{%m/%d/%y %H:%M:%S,%Q} [%t] %-5p - %m%n
2.logger的配置语法
包括rootLogger和non-root logger。
对于rootLogger来说:log4cplus.rootLogger=[LogLevel], appenderName, appenderName, ...
对于non-root logger来说:log4cplus.logger.logger_name=[LogLevel|INHERITED], appenderName, appenderName, ...
脚本方式使用起来非常简单,只要首先加载配置即可(urconfig.properties是自行定义的配置文件):
PropertyConfigurator::doConfigure("urconfig.properties");
下面我们通过例子体会一下log4cplus强大的基于脚本过滤log信息的功能。
〖例8〗
/* * urconfig.properties */log4cplus.rootLogger=TRACE, ALL_MSGS, TRACE_MSGS, DEBUG_INFO_MSGS, FATAL_MSGS
log4cplus.appender.ALL_MSGS=log4cplus::RollingFileAppenderlog4cplus.appender.ALL_MSGS.File=all_msgs.loglog4cplus.appender.ALL_MSGS.layout=log4cplus::TTCCLayout
log4cplus.appender.TRACE_MSGS=log4cplus::RollingFileAppenderlog4cplus.appender.TRACE_MSGS.File=trace_msgs.loglog4cplus.appender.TRACE_MSGS.layout=log4cplus::TTCCLayoutlog4cplus.appender.TRACE_MSGS.filters.1=log4cplus::spi::LogLevelMatchFilterlog4cplus.appender.TRACE_MSGS.filters.1.LogLevelToMatch=TRACElog4cplus.appender.TRACE_MSGS.filters.1.AcceptOnMatch=truelog4cplus.appender.TRACE_MSGS.filters.2=log4cplus::spi::DenyAllFilter
log4cplus.appender.DEBUG_INFO_MSGS=log4cplus::RollingFileAppenderlog4cplus.appender.DEBUG_INFO_MSGS.File=debug_info_msgs.loglog4cplus.appender.DEBUG_INFO_MSGS.layout=log4cplus::TTCCLayoutlog4cplus.appender.DEBUG_INFO_MSGS.filters.1=log4cplus::spi::LogLevelRangeFilterlog4cplus.appender.DEBUG_INFO_MSGS.filters.1.LogLevelMin=DEBUGlog4cplus.appender.DEBUG_INFO_MSGS.filters.1.LogLevelMax=INFOlog4cplus.appender.DEBUG_INFO_MSGS.filters.1.AcceptOnMatch=truelog4cplus.appender.DEBUG_INFO_MSGS.filters.2=log4cplus::spi::DenyAllFilter
log4cplus.appender.FATAL_MSGS=log4cplus::RollingFileAppenderlog4cplus.appender.FATAL_MSGS.File=fatal_msgs.loglog4cplus.appender.FATAL_MSGS.layout=log4cplus::TTCCLayoutlog4cplus.appender.FATAL_MSGS.filters.1=log4cplus::spi::StringMatchFilterlog4cplus.appender.FATAL_MSGS.filters.1.StringToMatch=FATALlog4cplus.appender.FATAL_MSGS.filters.1.AcceptOnMatch=truelog4cplus.appender.FATAL_MSGS.filters.2=log4cplus::spi::DenyAllFilter
/* * main.cpp */#include <log4cplus logger.h=""></log4cplus>#include <log4cplus configurator.h=""></log4cplus>#include <log4cplus helpers="" stringhelper.h=""></log4cplus><log4cplus></log4cplus>
using namespace log4cplus;
static Logger logger = Logger::getInstance("log");
void printDebug(){ LOG4CPLUS_TRACE_METHOD(logger, "::printDebug()"); LOG4CPLUS_DEBUG(logger, "This is a DEBUG message"); LOG4CPLUS_INFO(logger, "This is a INFO message"); LOG4CPLUS_WARN(logger, "This is a WARN message"); LOG4CPLUS_ERROR(logger, "This is a ERROR message"); LOG4CPLUS_FATAL(logger, "This is a FATAL message");}int main(){ Logger root = Logger::getRoot(); PropertyConfigurator::doConfigure("urconfig.properties"); printDebug();
return 0;}
运行结果:
1. all_msgs.log10-17-04 14:55:25,858 [1075298944] TRACE log <> - ENTER: ::printDebug()10-17-04 14:55:25,871 [1075298944] DEBUG log <> - This is a DEBUG message10-17-04 14:55:25,873 [1075298944] INFO log <> - This is a INFO message10-17-04 14:55:25,873 [1075298944] WARN log <> - This is a WARN message10-17-04 14:55:25,874 [1075298944] ERROR log <> - This is a ERROR message10-17-04 14:55:25,874 [1075298944] FATAL log <> - This is a FATAL message10-17-04 14:55:25,875 [1075298944] TRACE log <> - EXIT: ::printDebug()
2. trace_msgs.log10-17-04 14:55:25,858 [1075298944] TRACE log <> - ENTER: ::printDebug()10-17-04 14:55:25,875 [1075298944] TRACE log <> - EXIT: ::printDebug()
3. debug_info_msgs.log10-17-04 14:55:25,871 [1075298944] DEBUG log <> - This is a DEBUG message10-17-04 14:55:25,873 [1075298944] INFO log <> - This is a INFO message
4. fatal_msgs.log10-17-04 14:55:25,874 [1075298944] FATAL log <> - This is a FATAL message
本部分详细介绍了如何有选择地控制log信息的输出,最后一部分我们将介绍一下多线程、和C/S模式下该如何操作,顺便提一下NDC的概念。