转自 http://www.usidcbbs.com/simple/?t1514.html
C++日志库,log4cplus,log4cpp使用资料手册
1 下载log4cpp并解压。
2 打开\log4cpp-0.3.4b\msvc6\msvc6.dsw
编译log4cpp工程Release版。
3 将编译后的log4cpp.lib复制到VC的Lib目录中。
4 将头文件的目录log4cpp-0.3.4b\include\log4cpp\
复制到VC的Include目录.
(或者添加log4cpp-0.3.4b\include到VC的Include环境变量)
5 目标工程包含库
log4cpp.lib ws2_32.lib
复制到VC的Lib目录.
(要选择库连接方式相同的库)
6 包含头文件
日志记录
#include <log4cplus/logger.h>
日志配置读取
#include <log4cplus/consoleappender.h>
NDC
#include <log4cplus/loglevel.h>
日志代码
每个类可以有自己的类别(log4cpp::Category),可以在配置文件中添加该类别并设置日志级别。
所有的log4cpp::Category都使用同一个Appender,不同的Category配置为不同的日志级别,就可以控制日志输出的范围。
一般只使用四个记录日志级:DEBUG,INFO,WARN,ERROR
如:
log4cpp::Category::getRoot().info("Now run line %d", __LINE__);
或使用非根类别
log4cpp::Category::getInstance("MyCat").info("Now run line %d", __LINE__);
使用流:
log4cpp::Category::getInstance("main_cat").infoStream() << "This will show up as " << 1 << " emergency message" << log4cpp::CategoryStream::ENDLINE;
具体的函数说明见api文档.
7 读取配置代码
读取log配置文件,应在log4cpp静态成员初始化之后。
如在CXXXApp::InitInstance()中
try { log4cpp::PropertyConfigurator::configure("log.ini"); } catch (log4cpp::ConfigureFailure e) { log4cpp::Category::getRoot().warn(e.what()); }
8 配置文件
[log4cpp]
# Set root category priority to DEBUG and its only appender to A1.
# priority enum: "FATAL", "ALERT", "CRIT", "ERROR", "WARN",
# "NOTICE", "INFO", "DEBUG", "NOTSET", "UNKNOWN"
rootCategory=DEBUG,A1
additivity.rootCategory=false
# define appender
appender.A1=RollingFileAppender
#appender.A1.threshold=NOTSET
appender.A1.fileName=XXXX.log
#appender.A1.maxFileSize=10485760
#appender.A1.maxBackupIndex=1
appender.A1.layout=PatternLayout
appender.A1.layout.ConversionPattern=[%d{%Y-%m-%d %H:%M:%S}](%p)%c %x: %m%n
appender.Info_Cons=ConsoleAppender
appender.Info_Cons.threshold=INFO
appender.Info_Cons.layout=PatternLayout
appender.Info_Cons.layout.ConversionPattern=[%d{%Y-%m-%d %H:%M:%S}](%p)%c %x: %m%n
# category for sub1
category.sub1=DEBUG,A1,Info_Cons
additivity.sub1=false
category.sub2=INFO,A1
additivity.sub1=false
# other categories
[others]
djkf=dksajf
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对象会绑定到logger
6. 设置logger的优先级,如省略此步骤,各种有限级的消息都将被记录
下面通过一些例子来了解log4cplus的基本使用。
〖例1〗
#include "log4cplus/logger.h" #include "log4cplus/consoleappender.h" #include "log4cplus/loglevel.h" using namespace log4cplus; using namespace log4cplus::helpers; int main() { SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test"); std::string pattern = "%d{%m/%d/%y %H:%M:%S} - %m [%l]%n"; std::auto_ptr _layout(new PatternLayout(pattern)); _append->setLayout( _layout ); Logger _logger = Logger::getInstance("test"); _logger.addAppender(_append); _logger.setLogLevel(ALL_LOG_LEVEL); LOG4CPLUS_DEBUG(_logger, "This is the FIRST log message...") sleep(1); LOG4CPLUS_WARN(_logger, "This is the SECOND log message...") return 0; }
输出结果:
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〗
#include "log4cplus/logger.h" #include "log4cplus/consoleappender.h" #include "log4cplus/loglevel.h" using namespace log4cplus; using namespace log4cplus::helpers; int main() { SharedAppenderPtr _append(new ConsoleAppender()); _append->setName("append test"); Logger _logger = Logger::getInstance("test"); _logger.addAppender(_append); 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〗
#include "log4cplus/logger.h" #include "log4cplus/consoleappender.h" #include "log4cplus/loglevel.h" #include using namespace log4cplus; int main() { SharedAppenderPtr _append(new ConsoleAppender()); _append->setName("append test"); Logger _logger = Logger::getInstance("test"); _logger.addAppender(_append); 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〗
#include "log4cplus/logger.h" #include "log4cplus/consoleappender.h" #include "log4cplus/loglevel.h" 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() { 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〗
#include "log4cplus/logger.h" #include "log4cplus/consoleappender.h" #include "log4cplus/loglevel.h" using namespace log4cplus; int main() { SharedAppenderPtr _append(new FileAppender("Test.log")); _append->setName("file log test"); Logger _logger = Logger::getInstance("test.subtestof_filelog"); _logger.addAppender(_append); 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,分别是SimpleLayout、PatternLayout、和TTCCLayout。其中:
1. SimpleLayout
是一种简单格式的布局器,在输出的原始信息之前加上LogLevel和一个"-"。
比如以下代码片段:
... ... SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test"); std::auto_ptr _layout(new log4cplus::SimpleLayout()); _append->setLayout( _layout ); Logger _logger = Logger::getInstance("test"); _logger.addAppender(_append); LOG4CPLUS_DEBUG(_logger, "This is the simple formatted log message...") ... ...
将打印结果:
DEBUG - This is the simple formatted log message...
2. PatternLayout
是一种有词法分析功能的模式布局器,一提起模式就会想起正则表达式,这里的模式和正则表达式类似,但是
远比后者简单,能够对预定义的标识符(称为conversion specifiers)进行解析,转换成特定格式输出。以下
代码片段演示了如何使用PatternLayout:
... ... SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test"); std::string pattern = "%d{%m/%d/%y %H:%M:%S} - %m [%l]%n"; std::auto_ptr _layout(new PatternLayout(pattern)); _append->setLayout( _layout ); Logger _logger = Logger::getInstance("test_logger.subtest"); _logger.addAppender(_append); 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:
... ... SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test"); std::auto_ptr _layout(new TTCCLayout()); _append->setLayout( _layout ); Logger _logger = Logger::getInstance("test_logger"); _logger.addAppender(_append); LOG4CPLUS_DEBUG(_logger, "teststr") ... ...
输出结果:
10-16-04 19:08:27,501 [1075298944] DEBUG test_logger <> - teststr
当构造TTCCLayout对象时选择GMT时间格式时:
... ... 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(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(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则继承它的LogLevel
ALL_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" 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 LogLevel
FATAL - root: DEBUG
FATAL - test: DEBUG
FATAL - test.subtest: DEBUG
Setting test.subtest to WARN
FATAL - root: DEBUG
FATAL - test: DEBUG
FATAL - test.subtest: WARN
Setting test.subtest to TRACE
FATAL - root: DEBUG
FATAL - test: TRACE
FATAL - test.subtest: WARN
Setting test.subtest to NO_LEVEL
FATAL - root: DEBUG
FATAL - test: TRACE
FATAL - test.subtest: TRACE
create a logger test_bak, named "test_", too.
Setting test to INFO, so test_bak also be set to INFO
FATAL - test: INFO
FATAL - test_bak: INFO
下面的例子演示了如何通过设置LogLevel来控制用户的log信息输出:
〖例7〗
#include "log4cplus/logger.h" #include "log4cplus/consoleappender.h" #include "log4cplus/loglevel.h" 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(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 allowed
10-17-04 10:11:40,587 [1075298944] TRACE root <> - info
10-17-04 10:11:40,590 [1075298944] DEBUG root <> - info
10-17-04 10:11:40,591 [1075298944] INFO root <> - info
10-17-04 10:11:40,591 [1075298944] WARN root <> - info
10-17-04 10:11:40,592 [1075298944] ERROR root <> - info
10-17-04 10:11:40,592 [1075298944] FATAL root <> - info
trace-log and above allowed
10-17-04 10:11:40,593 [1075298944] TRACE root <> - info
10-17-04 10:11:40,593 [1075298944] DEBUG root <> - info
10-17-04 10:11:40,594 [1075298944] INFO root <> - info
10-17-04 10:11:40,594 [1075298944] WARN root <> - info
10-17-04 10:11:40,594 [1075298944] ERROR root <> - info
10-17-04 10:11:40,594 [1075298944] FATAL root <> - info
debug-log and above allowed
10-17-04 10:11:40,595 [1075298944] DEBUG root <> - info
10-17-04 10:11:40,595 [1075298944] INFO root <> - info
10-17-04 10:11:40,596 [1075298944] WARN root <> - info
10-17-04 10:11:40,596 [1075298944] ERROR root <> - info
10-17-04 10:11:40,596 [1075298944] FATAL root <> - info
info-log and above allowed
10-17-04 10:11:40,597 [1075298944] INFO root <> - info
10-17-04 10:11:40,597 [1075298944] WARN root <> - info
10-17-04 10:11:40,597 [1075298944] ERROR root <> - info
10-17-04 10:11:40,598 [1075298944] FATAL root <> - info
warn-log and above allowed
10-17-04 10:11:40,598 [1075298944] WARN root <> - info
10-17-04 10:11:40,598 [1075298944] ERROR root <> - info
10-17-04 10:11:40,599 [1075298944] FATAL root <> - info
error-log and above allowed
10-17-04 10:11:40,599 [1075298944] ERROR root <> - info
10-17-04 10:11:40,600 [1075298944] FATAL root <> - info
fatal-log and above allowed
10-17-04 10:11:40,600 [1075298944] FATAL root <> - info
log disabled
用户也可以自行定义LogLevel,操作比较简单,首先要定义LEVEL值,比如HELLO_LOG_LEVEL定义如下:
const LogLevel HELLO_LOG_LEVEL = 15000;
然后定义以下宏即可:
#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::ConsoleAppender log4cplus.appender.append_2=log4cplus::FileAppender log4cplus.appender.append_3=log4cplus::RollingFileAppender log4cplus.appender.append_4=log4cplus::DailyRollingFileAppender log4cplus.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::LogLevelMatchFilter log4cplus.appender.append_1.filters.1.LogLevelToMatch=TRACE log4cplus.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::PatternLayout log4cplus.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〗
log4cplus.rootLogger=TRACE, ALL_MSGS, TRACE_MSGS, DEBUG_INFO_MSGS, FATAL_MSGS log4cplus.appender.ALL_MSGS=log4cplus::RollingFileAppender log4cplus.appender.ALL_MSGS.File=all_msgs.log log4cplus.appender.ALL_MSGS.layout=log4cplus::TTCCLayout log4cplus.appender.TRACE_MSGS=log4cplus::RollingFileAppender log4cplus.appender.TRACE_MSGS.File=trace_msgs.log log4cplus.appender.TRACE_MSGS.layout=log4cplus::TTCCLayout log4cplus.appender.TRACE_MSGS.filters.1=log4cplus::spi::LogLevelMatchFilter log4cplus.appender.TRACE_MSGS.filters.1.LogLevelToMatch=TRACE log4cplus.appender.TRACE_MSGS.filters.1.AcceptOnMatch=true log4cplus.appender.TRACE_MSGS.filters.2=log4cplus::spi::DenyAllFilter log4cplus.appender.DEBUG_INFO_MSGS=log4cplus::RollingFileAppender log4cplus.appender.DEBUG_INFO_MSGS.File=debug_info_msgs.log log4cplus.appender.DEBUG_INFO_MSGS.layout=log4cplus::TTCCLayout log4cplus.appender.DEBUG_INFO_MSGS.filters.1=log4cplus::spi::LogLevelRangeFilter log4cplus.appender.DEBUG_INFO_MSGS.filters.1.LogLevelMin=DEBUG log4cplus.appender.DEBUG_INFO_MSGS.filters.1.LogLevelMax=INFO log4cplus.appender.DEBUG_INFO_MSGS.filters.1.AcceptOnMatch=true log4cplus.appender.DEBUG_INFO_MSGS.filters.2=log4cplus::spi::DenyAllFilter log4cplus.appender.FATAL_MSGS=log4cplus::RollingFileAppender log4cplus.appender.FATAL_MSGS.File=fatal_msgs.log log4cplus.appender.FATAL_MSGS.layout=log4cplus::TTCCLayout log4cplus.appender.FATAL_MSGS.filters.1=log4cplus::spi::StringMatchFilter log4cplus.appender.FATAL_MSGS.filters.1.StringToMatch=FATAL log4cplus.appender.FATAL_MSGS.filters.1.AcceptOnMatch=true log4cplus.appender.FATAL_MSGS.filters.2=log4cplus::spi::DenyAllFilter #include "log4cplus/logger.h" #include "log4cplus/consoleappender.h" #include "log4cplus/loglevel.h" 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.log
10-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 message
10-17-04 14:55:25,873 [1075298944] INFO log <> - This is a INFO message
10-17-04 14:55:25,873 [1075298944] WARN log <> - This is a WARN message
10-17-04 14:55:25,874 [1075298944] ERROR log <> - This is a ERROR message
10-17-04 14:55:25,874 [1075298944] FATAL log <> - This is a FATAL message
10-17-04 14:55:25,875 [1075298944] TRACE log <> - EXIT: ::printDebug()
2. trace_msgs.log
10-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.log
10-17-04 14:55:25,871 [1075298944] DEBUG log <> - This is a DEBUG message
10-17-04 14:55:25,873 [1075298944] INFO log <> - This is a INFO message
4. fatal_msgs.log
10-17-04 14:55:25,874 [1075298944] FATAL log <> - This is a FATAL message
本部分详细介绍了如何有选择地控制log信息的输出,最后一部分我们将介绍一下多线程、和C/S模式下该如何操作,顺便提一下NDC的概念。
log4cplus在很多方面做的都很出色,但是使用过程有些地方感觉不爽。在继续吹捧之前我先把不爽之处稍微提一提,然后继续介绍关于线程和套接字的知识。
### 一些可以改进之处 ###
1. 用户自定义LogLevel的实现机制不够开放
在第五篇中曾经介绍过如何实现用户自行定义LogLevel,为了实现比较理想的效果,甚至还需要改log4cplus的源代码。
2. 生成Logger对象的机制可以改进
我在使用时候,经常需要在不同的文件、函数中操作同一个logger,虽然log4cplus实现了树状存储以及根据
名称生成Logger,却没有充分利用这样的特点确保同一个名称对应的logger对象的唯一性,比如以下代码:
... ... Logger logger1 = Logger::getInstance("test"); Logger logger2 = Logger::getInstance("test"); Logger * plogger1 = &logger1; Logger * plogger2 = &logger2; std::cout << "plogger1: " << plogger1 << std::endl << "plogger2: " << plogger2 << std::endl; ... ...
运行结果:
plogger1: 0xbfffe5a0
plogger2: 0xbfffe580
从结果可以看出,明明是同一个Logger,但每次调用都会产生一个Logger副本,虽然结果是正确的(因为将存
储和操作分开了),但是资源有些浪费,我看了一下log4cplus的代码,其实可以按照如下方式实现(示意性
的):
#include "log4cplus/logger.h" #include "log4cplus/consoleappender.h" #include "log4cplus/loglevel.h" class Logger; class LoggerContainer { public: ~LoggerContainer(); Logger * getinstance(const std::string & strLogger); private: typedef std::map<:string,> LoggerMap; LoggerMap loggerPtrs; }; class Logger { public: Logger() {std::cout << "ctor of Logger " << std::endl; } ~Logger() {std::cout << "dtor of Logger " << std::endl; } static Logger * getInstance( const std::string & strLogger) { static LoggerContainer defaultLoggerContainer; return defaultLoggerContainer.getinstance(strLogger); } }; LoggerContainer::~LoggerContainer() { LoggerMap::iterator itr = loggerPtrs.begin(); for( ; itr != loggerPtrs.end(); ++itr ) { delete (*itr).second; } } Logger * LoggerContainer::getinstance(const std::string & strLogger) { LoggerMap::iterator itr = loggerPtrs.find(strLogger); if(itr != loggerPtrs.end()) { return (*itr).second; } else { Logger * plogger = new Logger(); loggerPtrs.insert(std::make_pair(strLogger, plogger)); return plogger; } } int main() { Logger * plogger1 = Logger::getInstance("test"); Logger * plogger2 = Logger::getInstance("test"); std::cout << "plogger1: " << plogger1 << std::endl << "plogger2: " << plogger2 << std::endl; return 0; }
运行结果:
ctor of Logger
plogger1: 0x804fc30
plogger2: 0x804fc30
dtor of Logger
这里的LoggerContainer相当于log4cplus中的Hierarchy类,结果可以看出,通过同一个名称可以获取相同的Logger实例。
还有一些小毛病比如RollingFileAppender和DailyRollingFileAppender的参数输入顺序可以调整成统一方式等等,就不细说了。
本部分提到了使用log4cplus时候感觉不爽的地方,最后一部分将介绍一下log4cplus中线程和套接字实现情况
经过短暂的熟悉过程,log4cplus已经被成功应用到了我的项目中去了,效果还不错,:)除了上文提及的功能之外,下面将介绍log4cplus提供的线程和套接字的使用情况。
### NDC ###
首先我们先了解一下log4cplus中嵌入诊断上下文(Nested Diagnostic Context),即NDC。对log系统而言,当输入源可能不止一个,而只有一个输出时,往往需要分辩所要输出消息的来源,比如服务器处理来自不同客户端的消息时就需要作此判断,NDC可以为交错显示的信息打上一个标记(stamp), 使得辨认工作看起来比较容易些,呵呵。这个标记是线程特有的,利用了线程局部存储机制,称为线程私有数据(Thread-specific
Data,或TSD)。 看了一下源代码,相关定义如下,包括定义、初始化、获取、设置和清除操作:
linux pthread
# define LOG4CPLUS_THREAD_LOCAL_TYPE pthread_key_t*
# define LOG4CPLUS_THREAD_LOCAL_INIT ::log4cplus::thread::createPthreadKey()
# define LOG4CPLUS_GET_THREAD_LOCAL_VALUE( key ) pthread_getspecific(*key)
# define LOG4CPLUS_SET_THREAD_LOCAL_VALUE( key, value ) pthread_setspecific(*key, value)
# define LOG4CPLUS_THREAD_LOCAL_CLEANUP( key ) pthread_key_delete(*key)
win32
# define LOG4CPLUS_THREAD_LOCAL_TYPE DWORD
# define LOG4CPLUS_THREAD_LOCAL_INIT TlsAlloc()
# define LOG4CPLUS_GET_THREAD_LOCAL_VALUE( key ) TlsGetValue(key)
# define LOG4CPLUS_SET_THREAD_LOCAL_VALUE( key, value ) \
TlsSetValue(key, static_cast(value))
# define LOG4CPLUS_THREAD_LOCAL_CLEANUP( key ) TlsFree(key)
使用起来比较简单,在某个线程中:
NDC& ndc = log4cplus::getNDC(); ndc.push("ur ndc string"); LOG4CPLUS_DEBUG(logger, "this is a NDC test"); ... ... ndc.pop(); ... ... LOG4CPLUS_DEBUG(logger, "There should be no NDC..."); ndc.remove();
当设定输出格式(Layout)为TTCCLayout时,输出如下:
10-21-04 21:32:58, [3392] DEBUG test - this is a NDC test
10-21-04 21:32:58, [3392] DEBUG test <> - There should be no NDC...
也可以在自定义的输出格式中使用NDC(用%x) ,比如:
... ... std::string pattern = "NDC:[%x] - %m %n"; std::auto_ptr _layout(new PatternLayout(pattern)); ... ... LOG4CPLUS_DEBUG(_logger, "This is the FIRST log message...") NDC& ndc = log4cplus::getNDC(); ndc.push("ur ndc string"); LOG4CPLUS_WARN(_logger, "This is the SECOND log message...") ndc.pop(); ndc.remove(); ... ...
输出如下:
NDC:[] - This is the FIRST log message...
NDC:[ur ndc string] - This is the SECOND log message...
另外一种更简单的使用方法是在线程中直接用NDCContextCreator:
NDCContextCreator _first_ndc("ur ndc string"); LOG4CPLUS_DEBUG(logger, "this is a NDC test")
不必显式地调用push/pop了,而且当出现异常时,能够确保push与pop的调用是匹配的。
### 线程 ###
线程是log4cplus中的副产品, 而且仅作了最基本的实现,使用起来也异常简单,只要且必须要在派生类中重载run函数即可:
class TestThread : public AbstractThread { public: virtual void run(); }; void TestThread::run() { ... ... }
log4cplus的线程没有考虑同步、死锁,有互斥,实现线程切换的小函数挺别致的:
void log4cplus::thread::yield() { #if defined(LOG4CPLUS_USE_PTHREADS) ::sched_yield(); #elif defined(LOG4CPLUS_USE_WIN32_THREADS) ::Sleep(0); #endif }
### 套接字 ###
套接字也是log4cplus中的副产品,在namespace log4cplus::helpers中,实现了C/S方式的日志记录。
1. 客户端程序需要做的工作:
SharedAppenderPtr _append(new SocketAppender(host, 8888, "ServerName")); Logger::getRoot().addAppender(_append); LOG4CPLUS_INFO(Logger::getRoot(), "This is a test: ")
【注】 这里对宏的调用其实是调用了SocketAppender::append,里面有一个数据传输约定,即先发送
一个后续数据的总长度,然后再发送实际的数据:
... ... SocketBuffer buffer = convertToBuffer(event, serverName); SocketBuffer msgBuffer(LOG4CPLUS_MAX_MESSAGE_SIZE); msgBuffer.appendSize_t(buffer.getSize()); msgBuffer.appendBuffer(buffer); ... ...
2. 服务器端程序需要做的工作:
ServerSocket serverSocket(port); Socket sock = serverSocket.accept();
此后即可用该sock进行数据read/write了,形如:
SocketBuffer msgSizeBuffer(sizeof(unsigned int)); if(!clientsock.read(msgSizeBuffer)) { return; } unsigned int msgSize = msgSizeBuffer.readInt(); SocketBuffer buffer(msgSize); if(!clientsock.read(buffer)) { return; }
为了将读到的数据正常显示出来,需要将SocketBuffer存放的内容转换成InternalLoggingEvent格式:
spi::InternalLoggingEvent event = readFromBuffer(buffer);
然后输出:
Logger logger = Logger::getInstance(event.getLoggerName()); logger.callAppenders(event);
【注】 read/write是按照阻塞方式实现的,意味着对其调用直到满足了所接收或发送的个数才返回。
一、安装
我看网上好多人介绍log4c安装的时候都说有两个步骤:先下载expat安装包并安装expat,然后下载log4c安装包并安装log4c。这么看来,log4c是依赖expat的。但是有时候我们不想使用的日志系统还要依赖别的库,毕竟现在的开源日志系统很多,这样一来log4c就没有那么大的优势了。所以我仔细看了log4c的README文档,发现log4c模块默认情况下是使用expat库来作为XML文件的解析器(因为log4c的配置文件默认是一个叫log4crc的XML文件),我们可以在运行配置文件的时候加上--without-expat选项就可以不使用expat库而使用log4c自定义的解析器,该解析器是使用lex/yacc的代码进行解析的。
安装步骤跟很多其他的库一样,都是三个步骤:
./configure
make
make install
我们可以在configure的时候加一些选项,如果要设置log4c的安装路径为/usr/local/log4c,我们就可以加--prefix=/usr/local/log4c,如果不想依赖expat解析器,我们可以加--without-expat。如果我们要指定软件运行的系统平台,交叉环境下,我们可以用--host选项来设置,如果运行在arm平台下就加--host=arm-linux,如果是运行在mips平台下就加--host=mips-linux。
如果安装完的时候出现了以下错误,不要着急:
../../src/log4c/.libs/liblog4c.so: undefined reference to `rpl_malloc'
../../src/log4c/.libs/liblog4c.so: undefined reference to `rpl_realloc'
解决方法如下:
修改log4c_build/log4c-1.2.1/src/config.h.in文件:
将201行的#undef malloc注释掉。
将204行的#undef realloc注释掉。
然后执行以下命令:
./configure(同样有必要的情况下加上相应的选项)
make clean
make
make install
二、介绍一下log4c的配置文件log4crc
log4c中有三个重要的概念, category, appender, layout。
category(类型)用于区分不同的logger, 其实它就是个logger。在一个程序中我们可以通过category来指定很多的logger,用于不同的目的。
appdender用于描述输出流,通过为category来指定一个appdender,可以决定将log信息来输出到什么地方去,比如stdout, stderr, 文件, 或者是socket等等。说说常见的两种,stdout是输出到控制台,文件当然就是输出到文件咯,在log4c中默认的是使用轮询文件保存日志,假如我们设定的文件名为wlanLog(配置文件中是appender节点的prefix属性),文件的maxsize设置为102400(Bytes),文件的maxnum为10(文件的最多个数),那么日志会保存在wlanLog.0文件中,当该文件的大小达到102400Bytes是就会自动保存到wlanLog.1文件中,依次类推,当文件的个数达到maxnum且文件已满,接下来会自动保存到wlanLog.0文件中,这样循环保存的方式就是轮询。
layout用于指定日志信息的格式,通过为appender来指定一个layout,可以决定log信息以何种格式来输出,比如是否有带有时间戳, 是否包含文件位置信息等,以及他们在一条log信息中的输出格式的等,一般有basic和dated两种。大家感兴趣可以分别去试一下看看日志有什么区别。
最后,说一下log4crc文件放在项目工程生成的目标文件的那个目录下。
三、使用
//初始化 log4c_init(); //获取一个已有的category,这个category(此处为WLAN_Console)必须先配置到配置文件中。 log4c_category_t* mycat = log4c_category_get("WLAN_Console"); //用该category进行日志输出,日志的类型为DEBUG,输出信息为 "Hello World!", log4c_category_log(mycat, LOG4C_PRIORITY_DEBUG, "Hello World!"); //去初始化 log4c_fini();
log4c_category_log的原型为:
log4c_category_log的原型为:
static LOG4C_INLINE void log4c_category_log(const log4c_category_t* a_category,
int a_priority,
const char* a_format,
...)
其中后面的日志输出的格式化字符串a_format跟printf的输出格式化字符串一样,后面的参量表也和printf一样。非常方便!
log4c的日志优先级有11个,在src/log4c/目录下的priority.h中。我们常用的也就error、warn、info、debug和trace。
/** * Predefined Levels of priorities. These correspond to the priority levels * used by syslog(3). **/ typedef enum { /** fatal */ LOG4C_PRIORITY_FATAL = 000, /** alert */ LOG4C_PRIORITY_ALERT = 100, /** crit */ LOG4C_PRIORITY_CRIT = 200, /** error */ LOG4C_PRIORITY_ERROR = 300, /** warn */ LOG4C_PRIORITY_WARN = 400, /** notice */ LOG4C_PRIORITY_NOTICE = 500, /** info */ LOG4C_PRIORITY_INFO = 600, /** debug */ LOG4C_PRIORITY_DEBUG = 700, /** trace */ LOG4C_PRIORITY_TRACE = 800, /** notset */ LOG4C_PRIORITY_NOTSET = 900, /** unknown */ LOG4C_PRIORITY_UNKNOWN = 1000 } log4c_priority_level_t;
有时候为了方便,我们可以将log4c_category_log用宏定义封装起来,这个网上有例子,我给个链接吧:http://www.cnblogs.com/jyli/archive/2010/02/11/1660606.html。我们也可以用一个函数封装起来,这个网上我没找到,我就把我作的封装分享一下吧,因为这个牵扯到变参函数的参数传递,所以可能好多刚接触到变参函数的童鞋不是很清楚。
/********************************************************************** 函数名称 : logOut 创建日期 : 2011-12-27 作者 : 丶小小小威 函数描述 : 将日志输出到控制台 输入参数 : const LOG_LEVEL level : 日志输出的级别 const char *format : 日志输出的格式化字符串 输出参数 : 无 返回值 : 无 **********************************************************************/ void CLogger::logOut(const LOG_LEVEL level, const char *format, ...) { char temp[MAX_LEN] = {0}; int ret = 0; va_list ap; va_start(ap, format); ret = vsnprintf(temp, MAX_LEN, format, ap); assert((-1<ret) && (MAX_LEN>ret)); switch (m_logMode) { case TO_CONSOLE_AND_FILE: //既输出到控制台又输出到文件 { if (m_consoleCategory && m_fileCategory) { log4c_category_log(m_consoleCategory, level, "%s", temp); log4c_category_log(m_fileCategory, level, "%s", temp); } break; } case TO_CONSOLE: //输出到控制台 { log4c_category_log(m_consoleCategory, level, "%s", temp); break; } case TO_FILE: //输出到文件 { log4c_category_log(m_fileCategory, level, "%s", temp); break; } default: break; } va_end(ap); }
说明:我这里的LOG_LEVEL是我自己定义的一个枚举类型,成员为常用的几种日志类型(或者说优先级),m_logMode是我Log模块封装类的一个成员变量,表示日志输出的方式,上面的三种。我想这个我就不用多说了吧,你懂的,= =!
下面调用的时候也很简单:
char c = '\x41'; char s[20]; const char *p = "How do you do"; int a = 1234; int ha = 12; int *i; i = &ha; float f = 3.141592653589; double x = 0.12345678987654321; strcpy(s, "Hello, Comrade"); logger->logOut(ERROR, "a=%d", a); /*结果输出十进制整数a=1234*/ printf("===============================> a=%d\n\n", a); logger->logOut(ERROR, "a=%6d", a); /*结果输出6位十进制整数a= 1234*/ printf("===============================> a=%6d\n\n", a); logger->logOut(ERROR, "a=%06d", a); /*结果输出6位十进制整数a=001234*/ printf("===============================> a=%06d\n\n", a); logger->logOut(ERROR, "a=%2d", a); /*a超过2位,按实际值输出a=1234*/ printf("===============================> a=%2d\n\n", a); logger->logOut(ERROR, "*i=%4d", *i); /*输出4位十进制整数×i= 12*/ printf("===============================> *i=%4d\n\n", *i); logger->logOut(ERROR, "*i=%-4d", *i); /*输出左对齐4位十进制整数×i=12*/ printf("===============================> *i=%-4d\n\n", *i); logger->logOut(ERROR, "i=%p", i); /*输出地址i=0xbf96538c*/ printf("===============================> i=%p\n\n", i); logger->logOut(ERROR, "f=%f", f); /*输出浮点数f=3.141593*/ printf("===============================> f=%f\n\n", f); logger->logOut(ERROR, "f=6.4f", f); /*输出6位其中小数点后4位的浮点数 f=3.1416*/ printf("===============================> f=6.4f\n", f); logger->logOut(ERROR, "x=%lf", x); /*输出长浮点数x=0.123457*/ printf("===============================> x=%lf\n\n", x); logger->logOut(ERROR, "x=%18.16lf", x); /*输出18位其中小数点后16位的长浮点数 x=0.1234567898765432*/ printf("===============================> x=%18.16lf\n\n", x); logger->logOut(ERROR, "c=%c", c); /*输出字符c=A*/ printf("===============================> c=%c\n\n", c); logger->logOut(ERROR, "c=%x", c); /*输出字符ASCII码值c=41*/ printf("===============================> c=%x\n\n", c); logger->logOut(ERROR, "s[]=%s", s); /*输出数组字符串s[]=Hello, Comrade*/ printf("===============================> s[]=%s\n\n", s); logger->logOut(ERROR, "s[]=%6.9s", s); /*输出最多9个字符的字符串s[]=Hello, Co*/ printf("===============================> s[]=%6.9s\n\n", s); logger->logOut(ERROR, "s=%p", s); /*输出数组字符串首字符地址s=FFBE*/ printf("===============================> s=%p\n\n", s); logger->logOut(ERROR, "*p=%s", p); /*输出指针字符串p=How do you do*/ printf("===============================> *p=%s\n\n", p); logger->logOut(ERROR, "p=%p", p); /*输出指针的值p=0x8049a70*/ printf("===============================> p=%p\n\n", p);
说明:logger是我写的日志封装类的一个对象,大家可以把上面的部分代码加到自己的程序中,看看输出结果和printf到底是不是一样?我测试过,完全一样,这样一看,使用log4c日志模块更让人一目了然,尤其是在一个比较大的项目中,有一个好的日志模块将是非常重要的!差点忘了还有个编译运行。
四、编译运行
编译的时候要链接上库和头文件。编译的格式如下:
mips-linux-g++ -I /usr/local/include/ -I /usr/local/include/libxml2/ logger.cpp main.cpp -o capture -L /usr/local/lib/ -lstdc++ -llog4c -lxml2
说明:我用的是mips,所以编译时使用mips-linux-g++,我的log4c安装默认的路径下:/usr/local。这里用到的libxml2是因为我涉及修改log4c的配置文件log4crc,是一个XML文件,我选择libxml2。在mips上运行的时候要先告诉mips库所在的位置,使用export LD_LIBRARY_PATH=“库所在的位置”,建议大家写个Makefile。
五、log4crc文件
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE log4c SYSTEM ""> <log4c version="1.2.1"> <config> <bufsize>0</bufsize> <debug level="2"/> <nocleanup>0</nocleanup> <reread>1</reread> </config> <category name="root" priority="notice"/> <category name="six13log.log" priority="error" appender="stdout"/> <!--输出到控制台--> <category name="WLAN_Console" priority="trace" appender="stdout"/> <!--保存日志到文件--> <category name="WLAN_File" priority="trace" appender="myrollingfileappender"/> <!--logdir为日志输出路径 prefix为文件名 layout为输出格式 --> <appender name="myrollingfileappender" type="rollingfile" logdir="." prefix="wlan_log" layout="dated" rollingpolicy="myrollingpolicy"/> <!--sizewin表示达到最大值后新建日志文件 值由maxsize设定,单位Bytes maxnum为最大文件数目--> <rollingpolicy name="myrollingpolicy" type="sizewin" maxsize="102400" maxnum="10"/> <appender name="stdout" type="stream" layout="basic"/> <appender name="stderr" type="stream" layout="dated"/> <appender name="syslog" type="syslog" layout="basic"/> <appender name="s13file" type="s13_file" layout="basic"/> <appender name="plain_stderr" type="s13_stderr" layout="none"/> <appender name="cat_stderr" type="s13_stderr" layout="catlayout"/> <appender name="xml_stderr" type="s13_stderr" layout="xmllayout"/> <appender name="user_stderr" type="s13_stderr" layout="userlayout"/> <layout name="basic" type="basic"/> <layout name="dated" type="dated"/> <layout name="catlayout" type="s13_cat"/> <layout name="xmllayout" type="s13_xml"/> <layout name="none" type="s13_none"/> <layout name="userlayout" type="s13_userloc"/> <category name="six13log.log.app.application2" priority="debug" appender="cat_stderr"/> <category name="six13log.log.app.application3" priority="debug" appender="user_stderr"/> <category name="six13log.log.app" priority="debug" appender="myrollingfileappender"/> <category name="log4c.examples.helloworld" priority="debug" appender="stdout"/> </log4c>
首先,从http://sourceforge.net/projects/log4cplus/files/log4cplus-stable/下载最新的版本,解压缩,用vs2008打开msvc8文件夹下的log4cplus.sln,并按照提示转换。在Solution Configurations下拉列表框中,会有Debug、Debug_Unicode、Release、Release_Unicode四种形式,分别选择进行编译,选中Solution ‘log4cplus’,点击Biuld Solution,会在每种形式下成功编译17个工程文件。在msvc8/Win32下的文件夹中生成相应的库文件和exe执行文件。
将相应的库和头文件包含到相应的目录中即可应用,若不想改变头文件中包含的头文件设置,还需自己创建一个项目属性表。
基本元素:
Logger:解决了在哪里使用日志的问题,日志模块,程序中唯一一个必须要使用的模块;
Appenders:解决了将日志信息最终输出到哪里去的问题,如控制台、文件、网络等;
Layout:解决了如何输出的问题,格式化输出信息;
Filter:解决了最终哪些类型的信息需要输出,比如DEBUG、WARR、TRACE、INFO、ERROR等;
一般步骤:
(1)、生成Appender对象;
(2)、生成Layout对象,并绑定到Appender(可选);
(3)、生成Logger对象;
(4)、设置Logger优先级(可选);
(5)、将需要关联Logger的Appender添加到Logger上;
(6)、使用Logger输出信息,所有大于设定的优先级的信息,并在所有挂接在该Logger对象上的Appender上以相应的Layout设定的格式显示出来;
LogLevels:TRACE、DEBUG、INFO、WARN、ERROR、FATAL;
Layout:BasicLayout、TTCCLayout、PatternLayout;
Appenders:ConsoleAppender、FileAppender、RollingFileAppender、DailyRollingFileAppender、SyslogAppender、NTEventLogAppender、SocketAppender、Win32DebugAppender;
Filter:DenyAllFilter、LogLevelMatchFilter、LogLevelRangeFilter、StringMatchFilter;
// TestLog4cplus.cpp : Defines the entry point for the console application. #include <string> #include <log4cplus/logger.h>//定义Log对象 #include <log4cplus/consoleappender.h>//输出到控制台 #include <log4cplus/fileappender.h>//输出到文件 #include <log4cplus/layout.h>//输出格式 using namespace std; using namespace log4cplus; using namespace log4cplus::helpers; int _tmain(int argc, _TCHAR* argv[]) { string strLogName1 = "E:\\Log4cplus\\TestLog1.log"; string strLogName2 = "E:\\Log4cplus\\TestLog2.log"; //定义控制台Appender SharedAppenderPtr pConsoleAppender(new ConsoleAppender()); //定义文件Appender SharedAppenderPtr pFileAppender(new FileAppender(LOG4CPLUS_TEXT(strLogName1), LOG4CPLUS_FSTREAM_NAMESPACE::ios::app, true)); SharedAppenderPtr pRollingFileAppender(new RollingFileAppender(LOG4CPLUS_TEXT(strLogName2), 1024*1024, 1, true)); //定义Layout,并绑定到Appender上 //pConsoleAppender->setLayout(std::auto_ptr<Layout>(new SimpleLayout())); //pFileAppender->setLayout(std::auto_ptr<Layout>(new SimpleLayout())); //pRollingFileAppender->setLayout(std::auto_ptr<Layout>(new SimpleLayout())); //pConsoleAppender->setLayout(std::auto_ptr<Layout>(new TTCCLayout())); //pFileAppender->setLayout(std::auto_ptr<Layout>(new TTCCLayout())); //pRollingFileAppender->setLayout(std::auto_ptr<Layout>(new TTCCLayout())); //log4cplus::tstring pattern = LOG4CPLUS_TEXT("%d{%m/%d/%y %H:%M:%S,%Q} [%t] %-5p %c{2} %%%x%% - %m [%l]%n"); //log4cplus::tstring pattern = LOG4CPLUS_TEXT("%D{%c} %p %% %m %% [%l]%n");//%D显示本地时间、%d显示标准时间 log4cplus::tstring pattern = LOG4CPLUS_TEXT("%D{%Y/%m/%d %X} %p %% %m %% [%l]%n"); pConsoleAppender->setLayout(std::auto_ptr<Layout>(new PatternLayout(pattern))); pFileAppender->setLayout(std::auto_ptr<Layout>(new PatternLayout(pattern))); pRollingFileAppender->setLayout(std::auto_ptr<Layout>(new PatternLayout(pattern))); //定义Logger Logger pTestLogger = Logger::getInstance(LOG4CPLUS_TEXT("LoggerName")); //设置Logger优先级 //pTestLogger.setLogLevel(ALL_LOG_LEVEL); //pTestLogger.setLogLevel(DEBUG_LOG_LEVEL); pTestLogger.setLogLevel(INFO_LOG_LEVEL); //pTestLogger.setLogLevel(WARN_LOG_LEVEL); //pTestLogger.setLogLevel(ERROR_LOG_LEVEL); //将需要关联Logger的Appender添加到Logger上 pTestLogger.addAppender(pConsoleAppender); pTestLogger.addAppender(pFileAppender); pTestLogger.addAppender(pRollingFileAppender); //输出日志信息 LOG4CPLUS_INFO(pTestLogger, "this is a info log..."); LOG4CPLUS_DEBUG(pTestLogger, "this is a debug log..."); LOG4CPLUS_ERROR(pTestLogger, "this is a error log..."); LOG4CPLUS_WARN(pTestLogger, "this is a warn log..."); return 0; }