log4cplus 的github地址:https://github.com/log4cplus/log4cplus
下载链接:log4cplus - Browse /log4cplus-stable/2.0.7 at SourceForge.net
官方文档:log4cplus / Wiki / Home
1.log4cplus配置
(1)打开解决方案
打开Visual Studio,进入 log4cplus-2.x\msvc14目录下,运行log4cplus.sln解决方案。
(2)进行相应设置
编译log4cplus需要注意三点:
①解决方案的平台与目标程序一致,这里选择的是x64;
②版本也要与目标程序一致,这里选择的是release版本;
③属性里面的字符集和目标程序一致;
选择log4cplus项目,右键——>属性——>配置属性——>常规——>字符集,选择Unicode字符集,如下图所示。(log4cplus默认使用多字节字符集,而VS新建项目默认使用Unicode字符集,如果不修改此处,则后面新建项目使用生成的dll时需要手动修改新建的项目为多字节字符集,否则会报错,总之两边统一用一种即可)
编译完成后,会在log4cplus-2.x\msvc14\x64\bin.Release文件夹下生成我们需要的log4cplus.lib和log4cplus.dll两个文件(我编译的是release版),如下图所示。
(3)目标程序的配置
将log4cplus-2.0.x目录下的include文件夹拷贝到我们的目标程序文件夹中,这里面是我们需要的头文件;在目标程序的属性里面设置头文件的包含目录。
设置lib文件的库目录以及将lib文件填入附加依赖项,如下图所示。
将dll文件放到程序的根目录:如果运行的是VS放置到和*.vcxproj一个文件夹下,如果运行的是*.exe,则和*.exe放置到一个文件夹下。
右键——>属性——>链接器——>输入——>附加依赖项——>加入log4cplus.lib,如下图所示。
如果不添加上述附加依赖项,则需要在代码开头中添加如下代码:
#pragma comment(lib, "log4cplus.lib")
2.头文件
主要类说明:
类名 | 说明 |
Filter | 过滤器,过滤输出消息 |
Layout | 布局器,控制输出消息的格式 |
Appender | 挂接器,与布局器和过滤器紧密配合,将特定格式的消息过滤后输出到所挂接的设备终端如屏幕,文件等 |
Logger | 记录器,保存并跟踪对象日志信息变更的实体,当你需要对一个对象进行记录时,就需要生成一个logger。 |
Hierarchy | 分类器,层次化的树型结构,用于对被记录信息的分类,层次中每一个节点维护一个logger的所有信息 |
LogLevel | 优先权,包括TRACE, DEBUG, INFO, WARNING, ERROR, FATAL。 |
主要头文件如下:
#include <log4cplus/logger.h> //获取表示记录句柄
#include <log4cplus/loggingmacros.h>//这个头文件声明日志记录宏。除此之外,它还声明了若干个标准日志级别:FATAL, ERROR, WARN, INFO, DEBUG, TRACE
#include <log4cplus/configurator.h>//此头文件声明类 BasicConfigurator。
#include <log4cplus/initializer.h>//这个头文件声明类 Initializer。
在log4cplus2中,上述类都已经包含在头文件log4cplus.h中了,因此只需要包含该头文件即可:
#include <log4cplus/log4cplus.h>
3.初始化Initializer
实例化该类会初始化log4cplus Initializer:
log4cplus::Initializer initializer;
该类还维护一个引用计数。该类可以被实例化多次。当此引用计数达到零时,在的最后一个实例被销毁后,它会关闭log4cplus内部。在log4cplus解除初始化后,无法重新初始化。
log4cplus尝试使用其他一些方法关闭其内部。但是,这意味着在main()退出后不能使用它。
4.基本配置BasicConfigurator
log4cplus::BasicConfigurator config;
config.configure();
这两行使用简单的布局配置根记录器。ConsoleAppender
log4cplus::Logger logger = log4cplus::Logger::getInstance(
LOG4CPLUS_TEXT("main"));
这里我们获得名为main的记录器的记录器句柄。上面使用的宏与Windows上的或宏具有相同的功能:如果定义了预处理器符号,它会在作为参数传递的字符串文字前面加上前缀,使其成为宽字符串文字。
LOG4CPLUS_WARN(logger, LOG4CPLUS_TEXT(“Hello, World!”));
这里我们调用宏来记录Hello,World!将消息输入主记录器。记录的消息将从主记录器传播到根记录器,根记录器连接了一个用于在控制台上打印的日志。在内部,这个宏使用C++字符串流来格式化hello, World!消息这样做的结果是,您可以使用所有标准的C++流操作器。
5.取消初始化
log4cplus试图取消初始化(deinitialize),并在退出后释放所有分配的资源。然而,同样,根据编译器、平台和运行时库的不同,这可能是不可能的。这就是为什么适当的去初始化是必要的。
在2.0及更高版本中,它由类的最后一个实例及其析构函数完成。在以前的版本中,调用是正确的关闭方法Logger::shutdown()
6.日志记录宏
宏在hood下使用C++字符串流。
7.日志级别
此示例显示了如何在运行时通过调整实例上的日志级别阈值来过滤日志消息。log4cplus的优先级由低到高:
- NOT_SET_LOG_LEVEL:接受缺省的LogLevel,如果有父logger则继承它的LogLevel;
- ALL_LOG_LEVEL:开放所有log信息输出
- TRACE_LOG_LEVEL:开放trace信息输出(即ALL_LOG_LEVEL)
- DEBUG_LOG_LEVEL:开放debug信息输出
- INFO_LOG_LEVEL:开放info信息输出
- WARN_LOG_LEVEL:开放warning信息输出
- ERROR_LOG_LEVEL:开放error信息输出
- FATAL_LOG_LEVEL:开放fatal信息输出
- OFF_LOG_LEVEL:关闭所有log信息输出
各个logger可以通过setLogLevel设置自己的优先级,当某个logger的LogLevel设置成NOT_SET_LOG_LEVEL时,该logger会继承父logger的优先级,另外,如果定义了重名的多个logger, 对其中任何一个的修改都会同时改变其它logger。
代码示例:
#include <log4cplus/logger.h>
#include <log4cplus/loglevel.h>
#include <log4cplus/loggingmacros.h>
#include <log4cplus/configurator.h>
#include <log4cplus/initializer.h>
#include <iomanip>
#include <iostream>
void printMessages(log4cplus::Logger const & logger)
{
//使用所有常用日志级别打印信息
LOG4CPLUS_TRACE(logger, "printMessages()");
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");
}
void thresholdTest(log4cplus::LogLevel ll)
{
log4cplus::Logger logger
= log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("main"));
//设置日志级别阈值
logger.setLogLevel(ll);
//打印信息
//log4cplus::tcout << log4cplus::getLogLevelManager().toString(ll) << std::endl;
printMessages(logger);
std::cout << std::endl;
}
int main()
{
//初始化
log4cplus::Initializer initializer;
//基本配置
log4cplus::BasicConfigurator config;
config.configure();
thresholdTest(log4cplus::TRACE_LOG_LEVEL);//开放trace信息输出
thresholdTest(log4cplus::DEBUG_LOG_LEVEL);//开放debug信息输出
thresholdTest(log4cplus::INFO_LOG_LEVEL);//开放info信息输出
thresholdTest(log4cplus::WARN_LOG_LEVEL);//开放warning信息输出
thresholdTest(log4cplus::ERROR_LOG_LEVEL);//开放error信息输出
thresholdTest(log4cplus::FATAL_LOG_LEVEL);//开放fatal信息输出
return 0;
}
8.Appender输出位置
log4cplus默认将输出到控制台,提供ConsoleAppender用于操作。log4cplus还提供了三个类用于文件操作,它们是FileAppender类、RollingFileAppender类、DailyRollingFileAppender类。
Appender会注册到Logger中,Logger在写日志时,通过继承机制遍历所有注册到它本身和其父节点的Appender(在additivity为true的情况下),调用doAppend()方法,实现日志的写入。在doAppend方法中,若当前Appender注册了Filter,则doAppend还会判断当前日志时候通过了Filter的过滤,通过了Filter的过滤后,如果当前Appender继承自SkeletonAppender,还会检查当前日志级别时候要比当前Appender本身的日志级别阀门要打,所有这些都通过后,才会将LoggingEvent实例传递给Layout实例以格式化成一行日志信息,最后写入相应的目的地,在这些操作中,任何出现的错误都由ErrorHandler字段来处理。
(1)控制台输出ConsoleAppender
参考上文,输出在控制台中。
(2)文件输出FileAppender
FileAppender::FileAppender( const tstring& filename_,std::ios_base::openmode mode_, bool immediateFlush_,bool createDirs_)
- filename:文件名;
- mode:文件类型,可选择的文件类型包括app、ate、binary、in、out、trunc,因为实际上只是对stl的一个简单包装,这里就不多讲了。缺省是trunc,表示将先前文件删除;
- immediateFlush:缓冲刷新标志,如果为true表示每向文件写一条记录就刷新一次缓存,否则直到FileAppender被关闭或文件缓存已满才更新文件,一般是要设置true的,比如你往文件写的过程中出现了错误(如程序非正常退出),即使文件没有正常关闭也可以保证程序终止时刻之前的所有记录都会被正常保存;
- createDirs:是否创建目录;
输出在文件中,参考代码:
#include <log4cplus/log4cplus.h>
int main()
{
//用Initializer类进行初始化
log4cplus::Initializer initializer;
//第1步:建立ConsoleAppender
log4cplus::SharedAppenderPtr appender(new log4cplus::FileAppender(LOG4CPLUS_TEXT("E:\\test.log")));
//第2步:设置Appender的名称和输出格式(SimpleLayout)
appender->setName(LOG4CPLUS_TEXT("console"));
log4cplus::tstring pattern = LOG4CPLUS_TEXT("%D{%m/%d/%y %H:%M:%S,%Q} [%t] %-5p %c - %m [%l]%n");
appender->setLayout(std::unique_ptr<log4cplus::Layout>(new log4cplus::PatternLayout(pattern)));
//第3步:得到一个Logger实例,并设置其日志输出等级阈值
log4cplus::Logger logger = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("test"));
logger.setLogLevel(log4cplus::INFO_LOG_LEVEL);
//第4步:为Logger实例添加ConsoleAppender
logger.addAppender(appender);
//第5步:使用宏将日志输出
LOG4CPLUS_INFO(logger, LOG4CPLUS_TEXT("Hello world"));
return 0;
}
同时输出到控制台和文件,代码示例:
#include <log4cplus/log4cplus.h>
int main()
{
//用Initializer类进行初始化
log4cplus::Initializer initializer;
//第1步:建立ConsoleAppender
log4cplus::SharedAppenderPtr consoleAppender(new log4cplus::ConsoleAppender);
log4cplus::SharedAppenderPtr fileAppender(new log4cplus::FileAppender(LOG4CPLUS_TEXT("E:\\test.log")));
//第2步:设置Appender的名称和输出格式(SimpleLayout)
consoleAppender->setName(LOG4CPLUS_TEXT("console"));
consoleAppender->setLayout(std::unique_ptr<log4cplus::Layout>(new log4cplus::SimpleLayout()));
fileAppender->setName(LOG4CPLUS_TEXT("console"));
log4cplus::tstring pattern = LOG4CPLUS_TEXT("%D{%m/%d/%y %H:%M:%S,%Q} [%t] %-5p %c - %m [%l]%n");
fileAppender->setLayout(std::unique_ptr<log4cplus::Layout>(new log4cplus::PatternLayout(pattern)));
//第3步:得到一个Logger实例,并设置其日志输出等级阈值
log4cplus::Logger logger = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("test"));
logger.setLogLevel(log4cplus::INFO_LOG_LEVEL);
//第4步:为Logger实例添加ConsoleAppender
logger.addAppender(consoleAppender);
logger.addAppender(fileAppender);
//第5步:使用宏将日志输出
LOG4CPLUS_INFO(logger, LOG4CPLUS_TEXT("Hello world"));
return 0;
}
(4)RollingFileAppender
RollingFileAppender可以实现滚动转储的文件操作功能:
RollingFileAppender类可以根据你预先设定的大小来决定是否转储,当超过该大小,后续log信息会另存到新文件中,除了定义每个记录文件的大小之外,你还要确定在RollingFileAppender类对象构造时最多需要多少个这样的记录文件(maxBackupIndex+1),当存储的文件数目超过maxBackupIndex+1时,会删除最早生成的文件,保证整个文件数目等于maxBackupIndex+1。然后继续记录。
RollingFileAppender(
const log4cplus::tstring& filename,
long maxFileSize = 10*1024*1024, // 10 MB
int maxBackupIndex = 1,
bool immediateFlush = true,
bool createDirs = false);
- filename:文件名;
- maxFileSize:文件的最大尺寸;
- maxBackupIndex:最大记录文件数;
- immediateFlush:缓冲刷新标志;
- createDirs:创建目录;
(4)DailyRollingFileAppender
DailyRollingFileAppender实现根据频度来决定是否转储的文件转储功能:
DailyRollingFileAppender类可以根据你预先设定的频度来决定是否转储,当超过该频度,后续log信息会另存到新文件中,这里的频度包括:MONTHLY(每月)、WEEKLY(每周)、DAILY(每日)、TWICE_DAILY(每两天)、HOURLY(每时)、MINUTELY(每分)。
需要指出的是这里的"频度"并不是你写入文件的速度,其实是否转储的标准并不依赖你写入文件的速度,而是依赖于写入的那一时刻是否满足了频度条件,即是否超过了以分钟、小时、周、月为单位的时间刻度,如果超过了就另存。
DailyRollingFileAppender(
const log4cplus::tstring& filename,
DailyRollingFileSchedule schedule = DAILY,
bool immediateFlush = true,
int maxBackupIndex = 10,
bool createDirs = false);
- filename:文件名;
- schedule:存储频度;
- immediateFlush:缓冲刷新标志;
- maxBackupIndex:最大记录文件数;
- createDirs:创建目录
其中schedule为一个枚举类型,如下
enum DailyRollingFileSchedule {
MONTHLY,
WEEKLY,
DAILY,
TWICE_DAILY,
HOURLY,
MINUTELY
};
(5)SocketAppender
log4cplus提供了SocketAppender,实现了C/S方式的日志记录,用于支持重定向到远程服务器。
①客户端程序需要做的工作
定义一个SocketAppender类型的挂接器
SharedAppenderPtr _append(new SocketAppender(host, 8888, "ServerName"));
把该挂接器加入到logger中
Logger::getRoot().addAppender(_append);
SocketAppender类型不需要Layout, 直接调用宏就可以将信息发往loggerServer了
LOG4CPLUS_INFO(Logger::getRoot(), "This is a test: ")
②服务器端程序需要做的工作
定义一个ServerSocket
ServerSocket serverSocket(port);
调用accept函数创建一个新的socket与客户端连接
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格式:
log4cplus::spi::InternalLoggingEvent event = readFromBuffer(buffer);
然后输出:
Logger logger = Logger::getInstance(event.getLoggerName()); logger.callAppenders(event);
注意:read/write是按照阻塞方式实现的,意味着对其调用直到满足了所接收或发送的个数才返回。
9.布局设置
log4cplus通过布局器(Layouts)来控制输出的格式,log4cplus提供了三种类型的Layouts,分别是SimpleLayout、PatternLayout、和TTCCLayout。
(1)SimpleLayout
一种简单格式的布局器,在输出的原始信息之前加上LogLevel和一个"-",如果初始化时没有将布局器附加到挂接器,则默认使用SimpleLayout。
(2)PatternLayout
一种有词法分析功能的模式布局器,类似于C语言的printf()函数,能够对预定义的转换标识符(conversion specifiers)进行解析,转换成特定格式输出。
以下代码片段演示了如何使用PatternLayout。
#include <log4cplus/log4cplus.h>
int main()
{
//用Initializer类进行初始化
log4cplus::Initializer initializer;
//第1步:建立ConsoleAppender
log4cplus::SharedAppenderPtr appender(new log4cplus::ConsoleAppender());
//第2步:设置Appender的名称和输出格式(SimpleLayout)
appender->setName(LOG4CPLUS_TEXT("console"));
log4cplus::tstring pattern = LOG4CPLUS_TEXT("%D{%m/%d/%y %H:%M:%S,%Q} [%t] %-5p %c - %m [%l]%n");
appender->setLayout(std::unique_ptr<log4cplus::Layout>(new log4cplus::PatternLayout(pattern)));
//第3步:得到一个Logger实例,并设置其日志输出等级阈值
log4cplus::Logger logger = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("test"));
logger.setLogLevel(log4cplus::INFO_LOG_LEVEL);
//第4步:为Logger实例添加ConsoleAppender
logger.addAppender(appender);
//第5步:使用宏将日志输出
LOG4CPLUS_INFO(logger, LOG4CPLUS_TEXT("Hello world"));
return 0;
}
PatterLayout支持的转换标识符主要包括:
- "%%",转义为%, 即,std::string pattern = “%%” 时输出"%"。
- "%c",输出logger名称,比如std::string pattern ="%c" 时输出: “test_logger.subtest”, 也可以控制logger名称的显示层次,比如"%c{1}“时输出"test_logger”,其中数字表示层次。
- "%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”
- "%F",输出当前记录器所在的文件名称,比如std::string pattern ="%F" 时输出: “main.cpp”。
- "%L",输出当前记录器所在的文件行号,比如std::string pattern ="%L" 时输出: “51”
- "%l",输出当前记录器所在的文件名称和行号,比如std::string pattern ="%L" 时输出"main.cpp:51"。
- "%m",输出原始信息,比如std::string pattern ="%m" 时输出: “teststr”,即上述代码中LOG4CPLUS_DEBUG的第二个参数,这种实现机制可以确保原始信息被嵌入到带格式的信息中。
- "%n",换行符,没什么好解释的。
- "%p",输出LogLevel,比如std::string pattern ="%p" 时输出: “DEBUG”。
- "%t",输出记录器所在的线程ID,比如std::string pattern ="%t" 时输出: “1075298944”。
- "%x",嵌套诊断上下文NDC (nested diagnostic context) 输出,从堆栈中弹出上下文信息,NDC可以用对不同源的log信息(同时地)交叉输出进行区分,关于NDC方面的详细介绍会在下文中提到。
- 格式对齐,比如std::string pattern ="%-10m"时表示左对齐,宽度是10,此时会输出"teststr “,当然其它的控制字符也可以相同的方式来使用,比如”%-12d","%-5p"等等。
(3)TTCCLayout
TTCCLayout是在PatternLayout基础上发展的一种缺省的带格式输出的布局器, 其格式由时间,线程ID,Logger和NDC 组成(consists of time, thread, Logger and nested diagnostic context information, hence the name),因而得名。
10.基本步骤
使用log4cplus有六个基本步骤:
- 实例化一个封装了输出介质的appender对象;
- 实例化一个封装了输出格式的layout对象;
- 将layout对象绑定(attach)到appender对象;如省略此步骤,简单布局器SimpleLayout(参见5.1小节)对象会绑定到logger。
- 实例化一个封装了日志输出logger对象,并调用其静态函数getInstance()获得实例,log4cplus::Logger::getInstance(“logger_name”);
- 将appender对象绑定(attach)到logger对象;
- 设置logger的优先级,如省略此步骤,各种有限级的日志都将被输出。
代码示例:
#include <log4cplus/log4cplus.h>
int main()
{
//用Initializer类进行初始化
log4cplus::Initializer initializer;
//第1步:创建ConsoleAppender(实例化一个appender对象)
log4cplus::SharedAppenderPtr appender(new log4cplus::ConsoleAppender());
//第2步:设置Appender的名称和输出格式(SimpleLayout)
appender->setName(LOG4CPLUS_TEXT("console"));
//第3步:实例化一个layout对象,将layout对象绑定到appender对象
appender->setLayout(std::unique_ptr<log4cplus::Layout>(new log4cplus::SimpleLayout));
//第4步:实例化一个封装了日志输出的Logger对象,并设置其日志输出等级阈值
log4cplus::Logger logger = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("test"));
//第5步:将appender对象绑定到logger对象
logger.addAppender(appender);
//第6步:设置日志log的优先级
logger.setLogLevel(log4cplus::INFO_LOG_LEVEL);
//使用宏将日志输出
LOG4CPLUS_INFO(logger, LOG4CPLUS_TEXT("Hello world"));
return 0;
}
11.注意事项
(1)如果使用ConsoleAppender()默认参数,那么控制台消息不会实时输出,需要等到缓冲区达到一定大小才会输出。当时这个可调试了好一会。因为我当时需要的效果就是实时输出到控制台与文件。代码如下:
log4cplus::SharedAppenderPtr append_console(new log4cplus::ConsoleAppender(false,true));
(2)输出数字、字符、字符串的方法如下:
//第5步:使用宏将日志输出
//数字输出
for(int i=0; i<10; ++i)
{
LOG4CPLUS_INFO(logger, "Entering loop #" << i);
}
//字符输出
char x='i';
LOG4CPLUS_INFO(logger, "Entering loop #" << x);
//字符串输出
std::string y="xyz";
LOG4CPLUS_INFO(logger, "Entering loop #" << LOG4CPLUS_C_STR_TO_TSTRING(y));