目录[-]
log4cplus是C++编写的开源的日志系统,前身是java编写的log4j系统,受Apache Software License保护,作者是Tad E. Smith。
log4cplus具有线程安全、灵活、以及多粒度控制的特点,通过将日志划分优先级使其可以面向程序调试、运行、测试、和维护等全生命周期。你可以选择将日志输出到屏幕、文件、NT event log、甚至是远程服务器;通过指定策略对日志进行定期备份等等。
1- 解压: gzip -cd log4cplus-x.x.x.tar.gz | tar -xf -
2- 进入log4cplus根目录: cd log4cplus-x.x.x
3- 产生Makefile: ./configure --prefix=/where/to/install -enable-threads=no
如果需要指定安装路径可使用--prefix参数, 否则将缺省安装到/usr/local目录下。另外,如果需要单线程版本可通过参数-enable-threads=no指定, 否则默认将安装多线程版本。
对于HP-UNIX平台用户, 由于aCC编译器选项兼容性问题,请另外加入参数CXXFLAGS=”-AA -w”(单线程版本)或CXXFLAGS=”-AA –mt -w”(多线程版本)。
4- 创建: make
对于HP-UNIX用户,由于aCC编译器不包含-Wall选项来显示所有警告,创建时将导致无效的-W参数错误,请修改/log4cplus-x.x.x/src目录下的Makefile,将AM_CPPFLAGS = -Wall 行的-Wall选项删除或注释掉。
此外,某些HP-UNIX平台的套接字连接接受函数accept()第三个参数要求为int*,而在socket-unix.cxx源文件153行实现中实际传入的是socklen_t*类型,平台并不支持,也将导致编译错误。解决方法是将源代码该行中的传入参数强制转换为int*类型即可。
注意AIX和Linux平台目前并没有上述两处创建错误。
对于AIX平台用户请保证创建时使用的编译器是xlC而不是g++,否则将导致log4cplus脚本配置功能运行时产生段异常,生成core文件。有鉴于此,也请保证HP-UNIX用户尽量使用aCC编译器进行创建。
5- 创建/log4cplus/tests目录下的测试用例: make check
6- 安装: make install
安装成功后将在/usr/local目录或指定的目录下创建include和lib两个子目录及相应文件。其中include目录包含头文件,lib目录包含最终打包生成的静态和动态库。在动态连接log4cplus库时请使用-llog4cplus选项。
类名 | 说明 |
Filter | 过滤器,过滤输出消息。过滤器,解决哪些信息需要输出的问题,比如DEBUG,WARR,INFO等的输出控制 |
Layout | 布局器,控制输出消息的格式。格式化输出信息,解决了如何输出的问题。 |
Appender | 挂接器,与布局器和过滤器紧密配合,将特定格式的消息过滤后输出到所挂接的设备终端如屏幕,文件等等)。接收日志的各个设备,如控制台、文件、网络等。解决了输出到哪里去的问题 |
Logger | 记录器,保存并跟踪对象日志信息变更的实体,当你需要对一个对象进行记录时,就需要生成一个logger。日志模块,程序中唯一一个必须得使用的模块,解决了在哪里使用日志的问题。 |
Hierarchy | 分类器,层次化的树型结构,用于对被记录信息的分类,层次中每一个节点维护一个logger的所有信息。 |
LogLevel | 优先权,包括TRACE, DEBUG, INFO, WARNING, ERROR, FATAL。 |
使用log4cplus有六个基本步骤:
l 实例化一个封装了输出介质的appender对象;
l 实例化一个封装了输出格式的layout对象;
l 将layout对象绑定(attach)到appender对象;如省略此步骤,简单布局器SimpleLayout(参见5.1小节)对象会绑定到logger。
l 实例化一个封装了日志输出logger对象,并调用其静态函数getInstance()获得实例,log4cplus::Logger::getInstance("logger_name");
l 将appender对象绑定(attach)到logger对象;
l 设置logger的优先级,如省略此步骤,各种有限级的日志都将被输出。
下面通过一些例子来了解log4cplus的基本使用。
/* *标准使用,严格实现步骤1-6。 */ #include <log4cplus/logger.h> #include <log4cplus/consoleappender.h> #include <log4cplus/layout.h>
using namespace log4cplus; using namespace log4cplus::helpers;
int main() { /* step 1: Instantiate an appender object */ SharedObjectPtr<Appender> _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> _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");
/* step 5: Attach the appender object to the logger */ _logger.addAppender(_append);
/* step 6: Set a priority for the logger */ _logger.setLogLevel(ALL_LOG_LEVEL);
/* log activity */ 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] |
/* *简洁使用,仅实现步骤1、4、5。 */ #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... |
/* *iostream模式,appender输出到控制台。 */ #include<log4cplus/logger.h> #include<log4cplus/consoleappender.h> #include<iomanip> usingnamespacelog4cplus;
int main() { /*step1:Instantiateanappenderobject*/ SharedAppenderPtr_append(new ConsoleAppender()); _append->setName("appendtest");
/*step4:Instantiatealoggerobject*/ Logger_logger = Logger::getInstance("test");
/*step5:Attachtheappenderobjecttothelogger*/ _logger.addAppender(_append);
/*logactivity*/ LOG4CPLUS_TRACE(_logger, "Thisis" << "justat" << "est." << std::endl) LOG4CPLUS_DEBUG(_logger, "Thisisabool:" << true) LOG4CPLUS_INFO(_logger, "Thisisachar:" << 'x') LOG4CPLUS_WARN(_logger, "Thisisaint:" << 1000) LOG4CPLUS_ERROR(_logger, "Thisisalong(hex):" << std::hex << 100000000) LOG4CPLUS_FATAL(_logger, "Thisisadouble:" << std::setprecision(15) << 1.2345234234)
return0; } |
输出结果:
DEBUG-Thisisabool:1 INFO-Thisisachar:x WARN-Thisisaint:1000 ERROR-Thisisalong(hex):5f5e100 FATAL-Thisisadouble:1.2345234234 |
/* *文件模式,appender输出到文件。 */ #include<log4cplus/logger.h> #include<log4cplus/fileappender.h> usingnamespacelog4cplus;
int main() { /*step1:Instantiateanappenderobject*/ SharedAppenderPtr_append(new FileAppender("Test.log")); _append->setName("filelogtest");
/*step4:Instantiatealoggerobject*/ Logger_logger = Logger::getInstance("test.subtestof_filelog");
/*step5:Attachtheappenderobjecttothelogger*/ _logger.addAppender(_append);
/*logactivity*/ for (int i = 0; i < 5; ++i) { LOG4CPLUS_DEBUG(_logger, "Enteringloop#" << i << "Endline#") }
return0; } |
输出结果(Test.log文件):
DEBUG-Enteringloop#0Endline# DEBUG-Enteringloop#1Endline# DEBUG-Enteringloop#2Endline# DEBUG-Enteringloop#3Endline# DEBUG-Enteringloop#4Endline# |
LogLog类实现了debug, warn, error 函数用于logcplus运行时显示log4cplus自身的调试、警告或错误信息,是对标准输出的简单封装,它也可以用来进行简单的日志输出。LogLog 同时提供了两个方法来进一步控制所输出的信息,其中setInternalDebugging()方法用来控制是否屏蔽输出信息中的调试信息,当输入参数为false则屏蔽,缺省设置为false。 setQuietMode()方法用来控制是否屏蔽所有输出信息,当输入参数为true则屏蔽,缺省设置为false。
/* 通过loglog来控制输出调试、警告或错误信息,appender输出到屏幕。 */ #include <iostream> #include <log4cplus/helpers/loglog.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; } |
输出结果:
EnteringprintMsgs()... log4cplus:WARNThisisaWarning... log4cplus:ERRORThisisaError... ExitingprintMsgs()... Turningondebug... EnteringprintMsgs()... log4cplus:ThisisaDebugstatement... log4cplus:WARNThisisaWarning... log4cplus:ERRORThisisaError... ExitingprintMsgs()... Turningonquietmode... EnteringprintMsgs()... ExitingprintMsgs()... |
注意输出信息中总是包含"log4cplus:"前缀,如果需要定制使其使用其他的前缀请参见9.2小节。
log4cplus在头文件loggingmacros.h中提供了以下的日志输出宏:
LOG4CPLUS_TRACE_METHOD(logger,logEvent)
LOG4CPLUS_TRACE(logger,logEvent) LOG4CPLUS_TRACE_STR(logger,logEvent)
LOG4CPLUS_DEBUG(logger,logEvent) LOG4CPLUS_DEBUG_STR(logger,logEvent)
LOG4CPLUS_INFO(logger,logEvent) LOG4CPLUS_INFO_STR(logger,logEvent)
LOG4CPLUS_WARN(logger,logEvent) LOG4CPLUS_WARN_STR(logger,logEvent)
LOG4CPLUS_ERROR(logger,logEvent) LOG4CPLUS_ERROR_STR(logger,logEvent)
LOG4CPLUS_FATAL(logger,logEvent) LOG4CPLUS_FATAL_STR(logger,logEvent) |
其中logger 为Logger实例名称,logEvent为日志内容。由于log4cplus选用C++的流机制进行日志输出,因此为了区分包含<<运算符和不包含<<运算符的日志内容,分别提供了LOG4CPLUS_XXXX和LOG4CPLUS_XXXX_STR两种日志输出宏。 另外,日志输出宏LOG4CPLUS_TRACE_METHOD主要用来跟踪方法的调用轨迹。
log4cplus通过布局器(Layouts)来控制输出的格式,log4cplus提供了三种类型的Layouts,分别是SimpleLayout、PatternLayout、和TTCCLayout。
一种简单格式的布局器,在输出的原始信息之前加上LogLevel和一个"-",如果初始化时没有将布局器附加到挂接器,则默认使用SimpleLayout。
以下代码片段演示了如何使用SimpleLayout。
... ... /* 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> _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... |
一种有词法分析功能的模式布局器,类似于C语言的printf()函数,能够对预定义的转换标识符(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> _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] |
PatterLayout支持的转换标识符主要包括:
(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"等等。
是在PatternLayout基础上发展的一种缺省的带格式输出的布局器,其格式由时间,线程ID,Logger和NDC 组成(consists of time, thread, Logger and nested diagnostic context information, hence the name),因而得名, 关于NDC请参见6.4小节。
以下代码片段演示了如何使用TTCCLayout。
...... /*step1:Instantiateanappenderobject*/ SharedObjectPtr_append(new ConsoleAppender()); _append->setName("appendfortest");
/*step2:Instantiatealayoutobject*/ std::auto_ptr_layout(new TTCCLayout());
/*step3:Attachthelayoutobjecttotheappender*/ _append->setLayout(_layout);
/*step4:Instantiatealoggerobject*/ Logger_logger=Logger::getInstance("test_logger");
/*step5:Attachtheappenderobjecttothelogger*/ _logger.addAppender(_append);
/*logactivity*/ LOG4CPLUS_DEBUG(_logger,"teststr") ...... |
输出结果:
10-16-04 19:08:27,501 [1075298944] DEBUG test_logger <> - teststr |
TTCCLayout在构造时,有机会选择显示本地时间或GMT时间,缺省是按照本地时间显示:TTCCLayout::TTCCLayout(bool use_gmtime = false)。
如果需要构造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 |
log4cplus默认将输出到控制台,提供ConsoleAppender用于操作。示例代码请参见4.2.1、4.2.2或4.2.3小节,这里不再赘述。
log4cplus提供了三个类用于文件操作,它们是FileAppender类、RollingFileAppender类、DailyRollingFileAppender类。
实现了基本的文件操作功能,构造函数如下:
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类的使用情况请参考4.2.5小节,这里不再赘述。
实现可以滚动转储的文件操作功能,构造函数如下:
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 < LOOP_COUNT; ++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。
实现根据频度来决定是否转储的文件转储功能,构造函数如下:
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 < LOOP_COUNT; ++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这样的文件。
需要指出的是这里的"频度"并不是你写入文件的速度,其实是否转储的标准并不依赖你写入文件的速度,而是依赖于写入的那一时刻是否满足了频度条件,即是否超过了以分钟、小时、周、月为单位的时间刻度,如果超过了就另存。
log4cplus提供了SocketAppender,实现了C/S方式的日志记录,用于支持重定向到远程服务器。
(1) 定义一个SocketAppender类型的挂接器
SharedAppenderPtr _append(new SocketAppender(host, 8888, "ServerName"));
(2) 把该挂接器加入到logger中
Logger::getRoot().addAppender(_append);
(3) SocketAppender类型不需要Layout, 直接调用宏就可以将信息发往loggerServer了LOG4CPLUS_INFO(Logger::getRoot(), "This is a test: ")
注意这里对宏的调用其实是调用了SocketAppender::append(),里面有一个数据传输约定,即先发送一个后续数据的总长度,然后再发送实际的数据:
... ... |