(一)log4cplusC++编写的开源的日志系统,功能非常全面,用到自己开发的工程中会比较专业的,:),本文介绍了log4cplus基本概念,以及如何安装,配置。


### 简介 ###


log4cplusC++编写的开源的日志系统,前身是java编写的log4j系统.Apache Software License保护。作者是Tad E. Smithlog4cplus具有线程安全、灵活、以及多粒度控制的特点,通过将信息划分优先级使其可以面向程序调试、运行、测试、和维护等全生命周期;你可以选择将信息输出到屏幕、文件、


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对象会绑定到logger


6. 设置logger的优先级,如省略此步骤,各种有限级的消息都将被记录


下面通过一些例子来了解log4cplus的基本使用。


〖例1


cpp 代码


1./*严格实现步骤1-6appender输出到屏幕, 其中的布局格式和LogLevel后面会详细解释。*/


2.#include <log4cplus\logger.h >


1.#include <log4cplus\consoleappender.h >


2.#include <log4cplus\layout.h >


3.using namespace log4cplus;


1.using namespace log4cplus::helpers;


1.int main()


{


1. /* step 1: Instantiate an appender object */


2. SharedObjectPtr<Appender> _append (new ConsoleAppender());


3. _append->setName("append for test");


4. /* step 2: Instantiate a layout object */


5. std::string pattern = "%d{%m/%d/%y %H:%M:%S} - %m [%l]%n";


6. std::auto_ptr<Layout> _layout(new PatternLayout(pattern));


7. /* step 3: Attach the layout object to the appender */


8. _append->setLayout( _layout );


9. /* step 4: Instantiate a logger object */


10. Logger _logger = Logger::getInstance("test");


11. /* step 5: Attach the appender object to the logger */


12. _logger.addAppender(_append);


13. /* step 6: Set a priority for the logger */


14. _logger.setLogLevel(ALL_LOG_LEVEL);


15. /* log activity */


16. LOG4CPLUS_DEBUG(_logger, "This is the FIRST log message...")


17. sleep(1);


18. LOG4CPLUS_WARN(_logger, "This is the SECOND log message...")


19. return 0;


20.}


输出结果:


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 >


#include <log4cplus\consoleappender.h >


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 >


#include <log4cplus\consoleappender.h >


#include <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: 1


INFO - This is a char: x


WARN - This is a int: 1000


ERROR - This is a long(hex): 5f5e100


FATAL - This is a double: 1.2345234234


〖例4


/*


调试模式,通过loglog来控制输出调试、警告或错误信息,appender输出到屏幕。


*/


#include <iostream>


#include <log4cplus\loglog.h\helpers >


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


/*


文件模式,appender输出到文件。*/


#include <log4cplus\logger.h>


#include <log4cplus\fileappender.h>


using namespace log4cplus;


int main(){


/* step 1: Instantiate an appender object */


SharedAppenderPtr _append(new FileAppender("Test.log"));


_append->setName("file log test");


/* step 4: Instantiate a logger object */


Logger _logger = Logger::getInstance("test.subtestof_filelog");


/* step 5: Attach the appender object to the logger */


_logger.addAppender(_append);


/* log activity */


int i;


for( i = 0; i < 5; ++i )


{


LOG4CPLUS_DEBUG(_logger, "Entering loop #" << i << "End line #")


}


return 0;


}


输出结果(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,分别是SimpleLayoutPatternLayout、和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基础上发展的一种缺省的带格式输出的布局器,其格式由时间,线程IDLoggerNDC 组成(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 : 文件类型,可选择的文件类型包括appatebinaryinouttrunc,因为实际上只是对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> (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.logTest.log.1Test.log.2Test.log.3Test.log.4Test.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> (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-03Test.log.2004-10-17-03-04Test.log.2004-10-17-03-05这样的文件。



需要指出的是,刚看到按照频度(如HOURLYMINUTELY)转储这样的概念,以为log4cplus提供了内部定时器,感觉很奇怪,因为日志系统不应该主动记录,而loging事件总是应该被动触发的啊。仔细看了源代码后才知道这里的"频度"并不是你写入文件的速度,其实是否转储的标准并不依赖你写入文件的速度,而是依赖于写入的那一时刻是否满足了频度条件,即是否超过了以分钟、小时、周、月为单位的时间刻度,如果超过了就另存。



本部分详细介绍log信息的几种文件操作方式,下面将重点介绍一下如何有选择地控制log信息的输出。