日志模块(一头文件就实现了日志记录)
温辉敏(wenhm@sina.com) 2009-8-2 晚
摘要:本文介绍了一个C/C++日志记录模块,它是对开源log4c进行的封装和修正的基础上[1][[2][3][4],将所有的代码都集成到一个.h头文件中。带来的革命性突破是日志模块的使用是那么的方便和简单:只要使用一个.h头文件即可无缝的将日志模块嵌入到你的工程中去,不需要再添加额外的的.c/.cpp或是链接lib/dll等,整个日志模块就是一个头文件。日志输出格式方面,提供了C语言传统的字符printf格式输出,同时也为C++提供了ostream方式的流式输出。
关键字: 日志 回滚 日志记录 log4c
下载源码:http://u.115.com/file/f5cb87b84a
日志记录模块.rar
或是csdn上搜索上述关键字即可找到。
1. 前言
一直想要找一个使用简单、功能能满足需求、空间占用小能同时能用于C和C++语言的日志记录模块,真是梦里寻她千百度,可依旧空山不语无一物。若像《林中路》述说的那样,有两条路供选择到还好,可问题是找不到路,只好在前人的基础上来进行一些封装(Wrapper)以达到目的了。
Log4Cxx/Log4Cpp等C++的库比较大,而且只能用于C++语言,C语言中无法使用。(其实可以使用C 函数来对C++代码进行封装,也可以达到在C语言中使用的目的的,只是自己懒没有去做这方面的工作。)
PWLIB、ACE等库中也有比较好的日志模块可因为和库关联性太大抽不出来,而且也是只能用于C++语言的。
于是发现log4c这个库还是不错的,C语言写的,整个编下来就几十K,可以支持C语言,至于C++语言只要外层进行下C++ 的ostream的流式封装就可以使用了。然后加入了PWLIB、ACE等库的一些比较简单适用的日志记录宏定义以方便适用,比如:
l 基本信息输出:带时间、文件名、行号、线程id、日志级别
l 以十六进制数字方式输出二进制数据块;
l 输出原始数据(不带时间、文件、行号、日志级别);
l 输出函数名;
l 条件日志输出(符合条件才会输出日志);
l 日志输出返回(日志输出后就return);
最后为了追求极限的使用方便,将所有的代码都放在一个头文件中,这样就不用在已有工程中添加日志模块的.c/.cpp,也不用链接日志模块的lib了。就一个头文件的包含就让日志模块无缝的嵌入到你的工程中。
2. 快速上手
2.1. C语言方式日志记录
下面是一个简单的使用日志模块的工程,工程中必须预定义了LOG4C_ENABLE 宏定义才能打开日志(通过该宏定义来控制是否打开日志),工程包含两个.c文件:main.c和test.c。采用Visual Stduio2005编译通过,代码可以去csdn上下载。
main.c的代码:
--------------------------------------------------------------------------------------------
#include <stdio.h>
/**下面两条语句将日志模块的实现代码加入进来
#define IMPLEMENT_LOG4C语句一定要在#include "log4c_amalgamation.h"语句之前。
*/
#define IMPLEMENT_LOG4C
#include "log4c_amalgamation.h"
extern void test();
void main(int argc, char **argv)
{
///日志模块缺省初始化
LOG4C_INIT_DEFAULT();
///进行日志输出
LOG4C((LOG_ERROR, "Hello world! My age is %d, and my name is %s!", 28, "Jess" ));
test();
///关闭日志模块防止内存/资源泄漏
LOG4C_FINI();
}
--------------------------------------------------------------------------------------------
test.c的代码:
--------------------------------------------------------------------------------------------
#include <stdio.h>
#include "log4c_amalgamation.h"
void test()
{
///进行日志输出
LOG4C((LOG_ERROR, "Hello world! My age is %d, and my name is %s!", 28, "Jess" ));
}
--------------------------------------------------------------------------------------------
2.2. C++语言方式日志记录
下面是一个简单的使用日志模块的工程,工程中必须预定义了LOG_USELOG4CXX宏定义才能打开日志(通过该宏定义来控制是否打开日志),工程包含两个.cpp文件:main.cpp和test.cpp。采用Visual Stduio2005编译通过,代码可以去csdn上下载。
main.cpp的代码:
--------------------------------------------------------------------------------------------
#include <stdio.h>
/**下面两条语句将日志模块的实现代码加入进来
#define IMPLEMENT_LOG4C语句一定要在#include "log4c_amalgamation.h"语句之前。
*/
#define IMPLEMENT_LOG4C
#include "log4c_amalgamation.h"
extern void test();
void main(int argc, char **argv)
{
///日志模块缺省初始化
LOG_INIT_DEFAULT();
///进行日志输出
LOG(LOG_LEVEL_FATAL, "test_log_int" << "you are the best one!");
test();
///关闭日志模块防止内存/资源泄漏
LOG_FINI();
}
--------------------------------------------------------------------------------------------
test.cpp的代码:
--------------------------------------------------------------------------------------------
#include <stdio.h>
#include "log4c_amalgamation.h"
void test()
{
///进行日志输出
LOG(LOG_LEVEL_ERROR, "Hello world! My age is" << 28 << " and my name is " << "Jess!" );
}
--------------------------------------------------------------------------------------------
3. 功能介绍
日志输出模块支持C的sprintf类型的fromat格式输出,同时也支持C++的流式输出。日志输出的基本内容格式如下:
20090803 11:39:41.656 ThreadID:5552 ERROR - test_usinglog4c.c(27) Hello world!
由上可见,一条日志输出信息中包含了时间(精确到毫秒)、线程ID、日志级别、文件名、行号、用户输出日志信息。
个人觉得时间和线程ID还是非常有用的,特别是线程ID,对于查多线程程序的死锁是非常有帮助的。笔者在中兴时的做的好几个局端项目中服务器程序出现死锁,导致Web HTTP访问无法返回,都是只能立刻使用WatchDog来重启服务器程序,因为不能影响服务。然后拿了打出来的日志分析个天昏地暗,因为有线程ID,总能找到是那几个线程死锁,进而进一步分析问题发生的原因以解决问题。
4. 使用介绍
本日志系统比较简单就一个头文件即可实现日志模块以进行日志的记录,若是就简单的记录日志只要看2.快速上手一节就可以了,下面进行下详细介绍。
4.1. C语言方式使用
4.1.1. 代码中加上日志模块定义
工程中任意一个*.c 文件(记住只要一个c文件有下面的代码就OK,如main函数所在c文件)包含如下代码:
#define IMPLEMENT_LOG4C
#include "log4c_amalgamation.h"
有了上面两行代码就可以将日志模块的代码实现加到你的代码中了,六个字:简单、快捷、方便。这样该.c文件中就可以正常的使用日志模块的所有宏定义来进行日志记录。
4.1.2. 包含日志模块头文件
除了4.1.1中的.c文件外,其它的.c文件都必须包含日志模块头文件,其它要记录日志的*.c 文件包含如下代码:
#include "log4c_amalgamation.h"
这样就可以使用日志模块的所有宏定义来进行日志记录了。
4.1.3. 宏定义开关控制是否打开日志记录
工程中要定义预定义宏定义LOG4C_ENABLE,可以通过控制该宏定义开关来关闭日志模块,若关闭该宏定义则整个工程和没有加入日志模块是一样的,所有的日志宏定义都将失效。
4.1.4. 日志模块初始化
日志模块初始化代码,必须调用日志模块初始化代码后才能正确记录日志,只有正确执行了日志模块初始化代码后日志宏定义才能正确进行日志记录。所有在日志模块初始化代码之前执行的日志宏定义都将不能正确记录日志。
日志模块初始化有多个可使用的初始化宏定义,可以按照你程序的特性来选择一个合适的初始化宏定义就好了,下面分别进行介绍。
4.1.4.1. 缺省日志初始化宏定义
LOG4C_INIT_DEFAULT();
上面的代码会自动在可执行程序目录下生成log4crc日志模块配置文件(若存在则不生成),配置文件中可以指定日志级别等(见.3.3.日志配置文件log4crc文件内容)。
采用这种方式,用户比较省心,不用去设置配置文件名和具体的配置文件中各个参数,将自动生成一个缺省的log4crc配置文件,日志级别是notice,所以只能输出比notice级别更高的日志信息。
示例代码:
LOG4C_INIT_DEFAULT();
4.1.4.2. 带参数设置日志初始化宏定义
LOG4C_INIT_WITH_PARAM();
上面的日志初始化宏定义是可以设置日志级别、日志配置文件、生成日志文件名等各个参数的,若不进行这些设置则采用缺省的参数,此时功能就和LOG4C_INIT_DEFAULT一样。
参数设置有如下宏定义,这些宏定义都必须在LOG4C_INIT_WITH_PARAM宏定义执行之前调用:
4.1.4.2.1. 设置日志配置文件名
LOG4C_PARAM_CFG_FILE_NAME(strCfgFileName)
参数strCfgFileName即为配置文件的名称,类型为字符串,这个允许用户指定日志配置文件名,不一定非要用缺省的log4crc。可以不调用该宏定义,此时使用缺省的log4crc来作为日志配置文件名。
4.1.4.2.2. 设置日志级别
LOG4C_PARAM_LOG_LEVEL(strLogLevel)
strLogLevel即为日志级别,类型为字符串,级别有fatal/alert/crit/error/warn/notice/info/debug/trace/notset/unknown。各个日志级别越来越弱,fatal表示只有fatal的日志才输出,alert表示只有fatal/alert级别的日志才输出,其它一次类推,unknown表输出所有级别的日志信息。可以不调用该宏定义,此时使用缺省”notice”来作为缺省的日志输出级别。
4.1.4.2.3. 设置生成的日志输出文件的文件前缀的名称
LOG4C_PARAM_LOG_FILE_NAME(strLogFileName)
strLogFileName即为文件名,类型为字符串。可以不调用该宏定义,此时使用缺省的”log”来作为生成的日志文件名的前缀,此时生成的日志输出文件为log.0、log.1等。最新的日志文件总是log.0,当达到限定大小时,将log.0改为log.1,原来的log.1改为log.2,原来的log.2改为log.3依次类推,然后重新开启一个新的log.0文件来存放日志。
4.1.4.2.4. 设置日志输出文件的大小
LOG4C_PARAM_LOG_FILE_SIZE(iFileSize)
iFileSize即为文件大小,类型为整型,单位为字节。当输出的日志文件达到该大小时将重新开启一个新的日志文件来存放后续的日志。可以不调用该宏定义,此时使用缺省的1048576字节即1M。
4.1.4.2.5. 设置生成的日志文件个数
LOG4C_PARAM_LOG_FILE_NUM(iFileNum)
设置至多生成多少个日志文件,类型为整型。当生成的日志文件已经达到最大个数时,将把最旧的日志覆盖掉。
4.1.4.2.6. 设置是否重新读取配置文件
LOG4C_PARAM_REREAD_LOG_CFG_FILE(bReReadLogCfgFile)
设置是否输出每条日志信息时都重新读取配置文件,类型为整型。这个在不希望关闭程序动态改变输出日志级别时特别有用,0表示否,1表示是。
4.1.4.2.7. 示例代码
///设置日志配置文件名
LOG4C_PARAM_CFG_FILE_NAME("MyLogCfgFile");
///设置生成日志文件名
LOG4C_PARAM_LOG_FILE_NAME("MyLogFile");
///设置日志级别
LOG4C_PARAM_LOG_LEVEL("unknown");
///设置日志文件大小
LOG4C_PARAM_LOG_FILE_SIZE(1024);
///设置生成日志文件个数,达到最大个数将自动覆盖最旧的日志
LOG4C_PARAM_LOG_FILE_NUM(5);
///设置每次记录日志都重新读取日志配置文件
LOG4C_PARAM_REREAD_LOG_CFG_FILE(1);
///带参数日志模块初始化,以上所有设置了的参数都将生效,没有设置的采用缺省值
LOG4C_INIT_WITH_PARAM();
4.1.4.3. 带参数设置日志初始化宏定义(多个进程实例)
LOG4C_INIT_WITH_PARAM_MULTI_PROCESS();
该宏定义作用和LOG4C_INIT_WITH_PARAM类似,只是用于多个进程实例时,设置参数也采用和LOG4C_INIT_WITH_PARAM一样的LOG4C_PARAM_CFG_FILE_NAME等宏定义来进行设置。
当有多个进程实例运行时,多个进程都会去访问日志配置文件,都会进行日志记录,此时生成的日志文件就是多个进程共同产生的了,容易导致混乱。此时可以采用LOG4C_INIT_WITH_PARAM_MULTI_PROCESS宏定义来进行初始化就好了。采用该宏定义,同一程序的多个进程实例间记录日志互相不影响。
假设LofCfgFile为设置的日志配置文件名,LogFile为设置的日志文件,则有多个进程实例时LofCfgFile1/LogFile1为第一个进程实例的日志配置文件名和生成的日志文件前缀;LofCfgFile2/LogFile2为第二个进程对应的,依次类推。
l 示例代码:
///设置日志配置文件名
LOG4C_PARAM_CFG_FILE_NAME("MyLogCfgFile");
///设置生成日志文件名
LOG4C_PARAM_LOG_FILE_NAME("MyLogFile");
///设置日志级别
LOG4C_PARAM_LOG_LEVEL("unknown");
///设置日志文件大小
LOG4C_PARAM_LOG_FILE_SIZE(1024);
///设置生成日志文件个数,达到最大个数将自动覆盖最旧的日志
LOG4C_PARAM_LOG_FILE_NUM(5);
///设置每次记录日志都重新读取日志配置文件
LOG4C_PARAM_REREAD_LOG_CFG_FILE(1);
///带参数日志模块初始化,以上所有设置了的参数都将生效,没有设置的采用缺省值
LOG4C_INIT_WITH_PARAM_MULTI_PROCESS ();
4.1.5. 日志模块扫尾代码
LOG4C_FINI();
日志模块扫尾代码,程序退出时调用扫尾代码防止内存/资源泄漏。
4.1.6. 日志记录
4.1.6.1. LOG4C宏定义记录日志
LOG4C((LOG_ERROR, "Hello world! My age is %d, and my name is %s!", 28, "Jess" ));
所有日志宏定义输出都采用和sprintf函数参数一样的方式,要注意的是这里前后是两个“(”和两个 “)”,切记切记。
l 日志级别有如下几种可选:
LOG_FATAL/LOG_ALERT/LOG_CRIT/LOG_ERROR/LOG_WARN/LOG_NOTICE/LOG_INFO/LOG_DEBUG/LOG_TRACE/LOG_NOTSET/LOG_UNKNOWN。
本宏定义的日志记录代码将输出时间、线程ID、日志级别、文件名、行号、日志输出信息,输出日志的具体内容如下:
20090803 11:39:41.625 ThreadID:5552 ERROR root- test_usinglog4c.c(25) Hello world! My age is 28, and my name is Jess!
4.1.6.2. LOG4C_FUN宏定义带函数名记录日志
LOG4C_FUN(("LOG4C_FUN"));
本宏定义输出日志中带有当前函数名称,输出日志的具体内容如下:
20090808 03:26:24.890 ThreadID:444 TRACE root- test.c(262) FUN<test_log4c_fun>:LOG4C_FUN
4.1.6.3. LOG4C_HEX_DUMP以十六进制方式输出缓冲区内容
char strBuffer[128] = "You are the Best One!/0 Yes.";
LOG4C_HEX_DUMP((LOG4C_TRACE, strBuffer, sizeof(strBuffer)));
本宏定义将缓冲区中数据以十六进制方式打印出来,要指定缓冲区首地址和缓冲区长度。
输出日志的具体内容如下:
20090808 03:26:24.937 ThreadID:444 TRACE root- test.c(296)
0000 59 6f 75 20 61 72 65 20 - 74 68 65 20 42 65 73 74 You are the Best
0010 20 4f 6e 65 21 00 20 59 - 65 73 2e 00 00 00 00 00 One!. Yes......
0020 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
0040 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
0050 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
0060 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
0070 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
l 示例代码:
///测试LOG4C_HEX_DUMP宏
void test_log4c_hex_dump()
{
char strBuffer[128] = "You are the Best One!/0 Yes.";
LOG4C_INIT_DEFAULT();
LOG4C_HEX_DUMP((LOG4C_PRIORITY_TRACE, strBuffer, sizeof(strBuffer)));
LOG4C_FINI();
}
4.1.6.4. LOG4C_IF宏定义条件记录日志
LOG4C_IF((4>3, LOG_ERROR, "LOG4C_IF:4>3"));
LOG4C_IF((2>3, LOG_ERROR, "LOG4C_IF:2>3"));
本宏定义只有在满足第一参数为真时才会进行日志输出,否则不输出日志。上面的两条语句就只有第一条才会输出日志,第二条由于条件为假将不输出为日志。
输出日志的具体内容如下:
20090808 03:26:24.921 ThreadID:444 ERROR root- test.c(272) LOG4C_IF:4>3
4.1.6.5. LOG4C_LINE宏定义输出当前行号
LOG4C_LINE();
本宏定义输出当前行号,输出日志的具体内容如下:
20090808 03:26:24.937 ThreadID:444 TRACE root- test.c(283) line:283
4.1.6.6. LOG4C_RETURN宏定义输出并return
LOG4C_RETURN((LOG4C_PRIORITY_TRACE, "LOG4C_RETURN"));
本宏定义输出当前日志信息后就return退出当前函数,输出日志的具体内容如下:
20090808 03:26:24.953 ThreadID:444 TRACE root- test.c(306) LOG4C_RETURN
4.1.6.7. LOG4C_RETURN_WITH_VALUE宏定义输出并带参数return
LOG4C_RETURN((LOG4C_PRIORITY_TRACE, "LOG4C_RETURN 0"));
本宏定义输出当前日志信息后就带参数return退出当前函数,输出日志的具体内容如下:
20090808 03:26:24.953 ThreadID:444 TRACE root- test.c(306) LOG4C_RETURN 0
4.1.6.8. LOG4C_ORIGIN宏定义输出用户原始信息日志
LOG4C_ORIGIN ((LOG_ERROR,"LOG4C_ORIGIN"));
本宏定义输出的日志就只有用户要输出的原始信息,没有其它时间、线程ID、日志级别等等额外信息了。输出日志的具体内容如下:
LOG4C_ ORIGIN
4.1.6.9. LOG4C_NO_FILENUM宏定义记录日志不带文件名和行号
LOG4C_NO_FILENUM((LOG_ERROR,"LOG4C_NO_FILENUM"));
本宏定义输出的日志除了不带文件名和行号,其它和LOG4C宏定义输出的日志都相同,输出日志的具体内容如下:
20090804 17:34:07.578 ThreadID:1384 ERROR root- LOG4C_NO_FILENUM
4.1.6.10. LOG4C_NO_FILENUM_NO_LAYOUT宏定义记录日志
LOG4C_NO_FILENUM_NO_LAYOUT((LOG_ERROR,"LOG4C_NO_FILENUM_NO_LAYOUT"));
本宏定义输出的日志就只有用户要输出的原始信息,没有其它时间、线程ID、日志级别等等额外信息了。本宏定义和LOG4C_ ORIGIN其实是一样的,输出日志的具体内容如下:
LOG4C_NO_FILENUM_NO_LAYOUT
4.1.6.11. LOG4C_BLOCK_BEGIN和LOG4C_BLOCK_END宏定义跟踪调用层次
LOG4C_BLOCK_BEGIN(("LOG4C_BLOCK_BEGIN0"));
LOG4C_BLOCK_BEGIN(("LOG4C_BLOCK_BEGIN1"));
LOG4C_BLOCK_BEGIN(("LOG4C_BLOCK_BEGIN2"));
LOG4C_BLOCK_BEGIN(("LOG4C_BLOCK_BEGIN3"));
LOG4C_BLOCK_END(("LOG4C_BLOCK_BEGIN3"));
LOG4C_BLOCK_END(("LOG4C_BLOCK_BEGIN2"));
LOG4C_BLOCK_END(("LOG4C_BLOCK_BEGIN1"));
LOG4C_BLOCK_END(("LOG4C_BLOCK_BEGIN0"));
LOG4C_BLOCK_BEGIN宏定义表示进入一层,LOG4C_BLOCK_END宏定义表退出一层,上面宏定义的日志输出信息为:
20090808 03:26:24.875 ThreadID:444 TRACE - test.c(244) B-Entry ==> LOG4C_BLOCK_BEGIN0
20090808 03:26:24.875 ThreadID:444 TRACE - test.c(245) B-Entry ====> LOG4C_BLOCK_BEGIN1
20090808 03:26:24.875 ThreadID:444 TRACE - test.c(246) B-Entry ======> LOG4C_BLOCK_BEGIN2
20090808 03:26:24.875 ThreadID:444 TRACE - test.c(247) B-Entry ========> LOG4C_BLOCK_BEGIN3
20090808 03:26:24.875 ThreadID:444 TRACE - test.c(249) B-Exit <======== LOG4C_BLOCK_BEGIN3
20090808 03:26:24.890 ThreadID:444 TRACE - test.c(250) B-Exit <====== LOG4C_BLOCK_BEGIN2
20090808 03:26:24.890 ThreadID:444 TRACE - test.c(251) B-Exit <==== LOG4C_BLOCK_BEGIN1
20090808 03:26:24.890 ThreadID:444 TRACE - test.c(252) B-Exit <== LOG4C_BLOCK_BEGIN0
看到没,以上日志的输出是有层次的,表示进入LOG4C_BLOCK_BEGIN0 Block后,又进入LOG4C_BLOCK_BEGIN1 Block直到LOG4C_BLOCK_BEGIN3 Block,然后开始以相反顺序退出各Block。
4.2. C++语言方式使用
4.2.1. 代码中加上日志模块定义
工程中任意一个*.cpp 文件(记住只要一个cpp文件有下面的代码就OK,如main函数所在cpp文件)包含如下代码:
#define IMPLEMENT_LOG4C
#include "log4c_amalgamation.h"
有了上面两行代码就可以将日志模块的代码实现加到你的代码中了,六个字:简单、快捷、方便。这样该.cpp文件中就可以正常的使用日志模块的所有宏定义来进行日志记录。
4.2.2. 包含日志模块头文件
除了4.2.1中的.cpp文件外,其它的.cpp文件都必须包含日志模块头文件,其它要记录日志的*.c pp文件包含如下代码:
#include "log4c_amalgamation.h"
这样就可以使用日志模块的所有宏定义来进行日志记录了。
4.2.3. 宏定义开关控制是否打开日志记录
工程中要定义预定义宏定义LOG_USELOG4CXX,可以通过控制该宏定义开关来关闭日志模块,若关闭该宏定义则整个工程和没有加入日志模块是一样的,所有的日志宏定义都将失效。
4.2.4. 日志模块初始化
日志模块初始化代码,必须调用日志模块初始化代码后才能正确记录日志,只有正确执行了日志模块初始化代码后日志宏定义才能正确进行日志记录。所有在日志模块初始化代码之前执行的日志宏定义都将不能正确记录日志。
日志模块初始化有多个可使用的初始化宏定义,可以按照你程序的特性来选择一个合适的初始化宏定义就好了,下面分别进行介绍。
4.2.4.1. 缺省日志初始化宏定义
LOG_INIT_DEFAULT();
上面的代码会自动在可执行程序目录下生成log4crc日志模块配置文件(若存在则不生成),配置文件中可以指定日志级别等(见.3.3.日志配置文件log4crc文件内容)。
采用这种方式,用户比较省心,不用去设置配置文件名和具体的配置文件中各个参数,将自动生成一个缺省的log4crc配置文件,日志级别是notice,所以只能输出比notice级别更高的日志信息。
示例代码:
LOG_INIT_DEFAULT();
4.2.4.2. 带参数设置日志初始化宏定义
LOG_INIT_WITH_PARAM();
上面的日志初始化宏定义是可以设置日志级别、日志配置文件、生成日志文件名等各个参数的,若不进行这些设置则采用缺省的参数,此时功能就和LOG_INIT_DEFAULT一样。
参数设置有如下宏定义,这些宏定义都必须在LOG_INIT_WITH_PARAM宏定义执行之前调用:
4.2.4.2.1. 设置日志配置文件名
LOG_PARAM_CFG_FILE_NAME (strCfgFileName)
参数strCfgFileName即为配置文件的名称,类型为字符串,这个允许用户指定日志配置文件名,不一定非要用缺省的log4crc。可以不调用该宏定义,此时使用缺省的log4crc来作为日志配置文件名。
4.2.4.2.2. 设置日志级别
LOG_PARAM_LOG_LEVEL (strLogLevel)
strLogLevel即为日志级别,类型为字符串,级别有fatal/alert/crit/error/warn/notice/info/debug/trace/notset/unknown。各个日志级别越来越弱,fatal表示只有fatal的日志才输出,alert表示只有fatal/alert级别的日志才输出,其它一次类推,unknown表输出所有级别的日志信息。可以不调用该宏定义,此时使用缺省”notice”来作为缺省的日志输出级别。
4.2.4.2.3. 设置生成的日志输出文件的文件前缀的名称
LOG_PARAM_LOG_FILE_NAME (strLogFileName)
strLogFileName即为文件名,类型为字符串。可以不调用该宏定义,此时使用缺省的”log”来作为生成的日志文件名的前缀,此时生成的日志输出文件为log.0、log.1等。最新的日志文件总是log.0,当达到限定大小时,将log.0改为log.1,原来的log.1改为log.2,原来的log.2改为log.3依次类推,然后重新开启一个新的log.0文件来存放日志。
4.2.4.2.4. 设置日志输出文件的大小
LOG_PARAM_LOG_FILE_SIZE(iFileSize)
iFileSize即为文件大小,类型为整型,单位为字节。当输出的日志文件达到该大小时将重新开启一个新的日志文件来存放后续的日志。可以不调用该宏定义,此时使用缺省的1048576字节即1M。
4.2.4.2.5. 设置生成的日志文件个数
LOG_PARAM_LOG_FILE_NUM(iFileNum)
设置至多生成多少个日志文件,类型为整型。当生成的日志文件已经达到最大个数时,将把最旧的日志覆盖掉。
4.2.4.2.6. 设置是否重新读取配置文件
LOG_PARAM_REREAD_LOG_CFG_FILE(bReReadLogCfgFile)
设置是否输出每条日志信息时都重新读取配置文件,类型为整型。这个在不希望关闭程序动态改变输出日志级别时特别有用,0表示否,1表示是。
4.2.4.2.7. 示例代码
///设置日志配置文件名
LOG_PARAM_CFG_FILE_NAME("test_log_with_param_macro");
///设置生成日志文件名
LOG_PARAM_LOG_FILE_NAME("test_log_with_param_macro");
///设置日志级别
LOG_PARAM_LOG_LEVEL("unknown");
///设置日志文件大小
LOG_PARAM_LOG_FILE_SIZE(512);
///设置生成日志文件个数,达到最大个数将自动覆盖最旧的日志
LOG_PARAM_LOG_FILE_NUM(5);
///设置每次记录日志都重新读取日志配置文件
LOG_PARAM_REREAD_LOG_CFG_FILE(1);
///带参数日志模块初始化,以上所有设置了的参数都将生效,没有设置的采用缺省值
LOG_INIT_WITH_PARAM();
4.2.4.3. 带参数设置日志初始化宏定义(多个进程实例)
LOG_INIT_WITH_PARAM_MULTI_PROCESS ();
该宏定义作用和LOG_INIT_WITH_PARAM类似,只是用于多个进程实例时,设置参数也采用和LOG_INIT_WITH_PARAM一样的LOG_PARAM_CFG_FILE_NAME等宏定义来进行设置。
当有多个进程实例运行时,多个进程都会去访问日志配置文件,都会进行日志记录,此时生成的日志文件就是多个进程共同产生的了,容易导致混乱。此时可以采用LOG_INIT_WITH_PARAM_MULTI_PROCESS宏定义来进行初始化就好了。采用该宏定义,同一程序的多个进程实例间记录日志互相不影响。
假设LofCfgFile为设置的日志配置文件名,LogFile为设置的日志文件,则有多个进程实例时LofCfgFile1/LogFile1为第一个进程实例的日志配置文件名和生成的日志文件前缀;LofCfgFile2/LogFile2为第二个进程对应的,依次类推。
l 示例代码:
///设置日志配置文件名
LOG_PARAM_CFG_FILE_NAME("test_log_with_param_multi_process_macro");
///设置生成日志文件名
LOG_PARAM_LOG_FILE_NAME("test_log_with_param_multi_process_macro");
///设置日志级别
LOG_PARAM_LOG_LEVEL("unknown");
///设置日志文件大小
LOG_PARAM_LOG_FILE_SIZE(512);
///设置生成日志文件个数,达到最大个数将自动覆盖最旧的日志
LOG_PARAM_LOG_FILE_NUM(5);
///设置每次记录日志都重新读取日志配置文件
LOG_PARAM_REREAD_LOG_CFG_FILE(1);
///带参数日志模块初始化,以上所有设置了的参数都将生效,没有设置的采用缺省值
LOG_INIT_WITH_PARAM_MULTI_PROCESS();
4.2.5. 日志模块扫尾代码
LOG_FINI();
日志模块扫尾代码,程序退出时调用扫尾代码防止内存/资源泄漏。
4.2.6. 日志记录
C++方式目前的日志输出支持了基本元素的流输出,另支持std::string,std::wstring两个类型数据的输出。
4.2.6.1. LOG宏定义记录日志
LOG(LOG_LEVEL_FATAL, "Hello world! My age is " << 28 <<", and my name is " << "Jess!");
l 日志级别有如下几种可选:
LOG_LEVEL_FATAL/LOG_LEVEL_ALERT/LOG_LEVEL_CRIT/LOG_LEVEL_ERROR/LOG_LEVEL_WARN/LOG_LEVEL_NOTICE/LOG_LEVEL_INFO/LOG_LEVEL_DEBUG/LOG_LEVEL_TRACE/LOG_LEVEL_NOTSET/LOG_LEVEL_UNKNOWN。
本宏定义的日志记录代码将输出时间、线程ID、日志级别、文件名、行号、日志输出信息,输出日志的具体内容如下:
20090803 11:39:41.625 ThreadID:5552 ERROR root- test_usinglog4c.c(25) Hello world! My age is 28, and my name is Jess!
4.2.6.2. LOG_PRINTF宏定义采用C语言方式printf参数格式记录日志
LOG_PRINTF((LOG_LEVEL_FATAL, " Hello world! My age is %d, and my name is %s!", 28, "Jess" ));
本宏定义输出都采用和sprintf函数参数一样的方式,要注意的是这里前后是两个“(”和两个 “)”,切记切记。输出的日志的具体内容如下:
20090803 11:39:41.625 ThreadID:5552 ERROR root- test_usinglog4c.c(25) Hello world! My age is 28, and my name is Jess!
4.2.6.3. LOG_FUN宏定义带函数名记录日志
LOG_FUN("LOG_FUN");
本宏定义输出日志中带有当前函数名称,输出日志的具体内容如下:
20090808 03:26:24.890 ThreadID:444 TRACE root- test.c(262) FUN<test_log4c_fun>:LOG_FUN
4.2.6.4. LOG_HEX_DUMP以十六进制方式输出缓冲区内容
char strBuffer[128] = "You are the Best One!/0 Yes.";
LOG_HEX_DUMP((LOG4C_TRACE, strBuffer, sizeof(strBuffer)));
本宏定义将缓冲区中数据以十六进制方式打印出来,要指定缓冲区首地址和缓冲区长度。
输出日志的具体内容如下:
20090808 03:26:24.937 ThreadID:444 TRACE root- test.c(296)
0000 59 6f 75 20 61 72 65 20 - 74 68 65 20 42 65 73 74 You are the Best
0010 20 4f 6e 65 21 00 20 59 - 65 73 2e 00 00 00 00 00 One!. Yes......
0020 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
0040 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
0050 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
0060 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
0070 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
l 示例代码:
///测试LOG4C_HEX_DUMP宏
void test_log_hex_dump()
{
char strBuffer[128] = "You are the Best One!/0 Yes.";
LOG_INIT_DEFAULT();
LOG_HEX_DUMP(LOG4C_PRIORITY_TRACE, strBuffer, sizeof(strBuffer));
LOG_FINI();
}
4.2.6.5. LOG_IF宏定义条件记录日志
LOG_IF(4>3, LOG_LEVEL_ERROR, "LOG_IF:4>3");
LOG_IF(2>3, LOG_LEVEL_ERROR, "LOG_IF:2>3");
本宏定义只有在满足第一参数为真时才会进行日志输出,否则不输出日志。上面的两条语句就只有第一条才会输出日志,第二条由于条件为假将不输出为日志。
输出日志的具体内容如下:
20090808 03:26:24.921 ThreadID:444 ERROR root- test.c(272) LOG_IF:4>3
4.2.6.6. LOG_LINE宏定义输出当前行号
LOG_LINE();
本宏定义输出当前行号,输出日志的具体内容如下:
20090808 03:26:24.937 ThreadID:444 TRACE root- test.c(283) line:283
4.2.6.7. LOG_RETURN宏定义输出并return
LOG_RETURN(LOG4C_LEVEL_TRACE, "LOG_RETURN");
本宏定义输出当前日志信息后就return退出当前函数,输出日志的具体内容如下:
20090808 03:26:24.953 ThreadID:444 TRACE root- test.c(306) LOG_RETURN
4.2.6.8. LOG_RETURN_WITH_VALUE宏定义输出并带参数return
LOG_RETURN(LOG4C_LEVEL_TRACE, "LOG_RETURN 0");
本宏定义输出当前日志信息后就带参数return退出当前函数,输出日志的具体内容如下:
20090808 03:26:24.953 ThreadID:444 TRACE root- test.c(306) LOG_RETURN 0
4.2.6.9. LOG_ORIGIN宏定义输出用户原始信息日志
LOG_ORIGIN (LOG_LEVEL_ERROR,"LOG_ORIGIN");
本宏定义输出的日志就只有用户要输出的原始信息,没有其它时间、线程ID、日志级别等等额外信息了。输出日志的具体内容如下:
LOG_ ORIGIN
4.2.6.10. LOG_NO_FILENUM宏定义记录日志不带文件名和行号
LOG_NO_FILENUM(LOG_LEVEL_ERROR,"LOG_NO_FILENUM");
本宏定义输出的日志除了不带文件名和行号,其它和LOG4C宏定义输出的日志都相同,输出日志的具体内容如下:
20090804 17:34:07.578 ThreadID:1384 ERROR root- LOG_NO_FILENUM
4.2.6.11. LOG_NO_FILENUM_NO_LAYOUT宏定义记录日志
LOG_NO_FILENUM_NO_LAYOUT(LOG_LEVEL_ERROR,"LOG_NO_FILENUM_NO_LAYOUT");
本宏定义输出的日志就只有用户要输出的原始信息,没有其它时间、线程ID、日志级别等等额外信息了。本宏定义和LOG4C_ ORIGIN其实是一样的,输出日志的具体内容如下:
LOG_NO_FILENUM_NO_LAYOUT
4.2.6.12. LOG_BLOCK宏定义跟踪调用层次
LOG_BLOCK ("LOG_BLOCK0");
LOG_BLOCK ("LOG_BLOCK1");
LOG_BLOCK ("LOG_BLOCK2");
LOG_BLOCK ("LOG_BLOCK3");
LOG_BLOCK宏定义将生成一个对象,利用对象的构造函数和析构函数来打印输出,构造函数调用时表示进入一层,析构函数调用时表退出一层,上面宏定义的日志输出信息为:
20090808 03:26:24.875 ThreadID:444 TRACE - test.c(244) B-Entry ==> LOG_BLOCK0
20090808 03:26:24.875 ThreadID:444 TRACE - test.c(245) B-Entry ====> LOG_BLOCK1
20090808 03:26:24.875 ThreadID:444 TRACE - test.c(245) B-Entry ======> LOG_BLOCK2
20090808 03:26:24.875 ThreadID:444 TRACE - test.c(245) B-Entry ========> LOG_BLOCK3
20090808 03:26:24.890 ThreadID:444 TRACE - test.c(251) B-Exit <======== LOG4C_BLOCK3
20090808 03:26:24.890 ThreadID:444 TRACE - test.c(251) B-Exit <==== ==LOG4C_BLOCK2
20090808 03:26:24.890 ThreadID:444 TRACE - test.c(251) B-Exit <==== LOG4C_BLOCK1
20090808 03:26:24.890 ThreadID:444 TRACE - test.c(252) B-Exit <== LOG4C_BLOCK0
看到没,以上日志的输出是有层次的,表示进入LOG4C_BLOCK0 Block后,又进入LOG4C_BLOCK1 Block直到LOG4C_BLOCK3 Block,然后开始以相反顺序退出各Block。
4.3. 日志配置文件log4crc文件内容
log4crc配置文件为log4c日志模块的缺省日志配置文件,文件内容见后面,采用XML格式。用户可以修改该配置文件来动态进行日志级别等的配置,详细的配置文件中各字段的格式可以去查看log4c的文档说明(http://log4c.sourceforge.net/),这里进行平时用的比较多的一些字段的简单介绍:
l priority的值即为日志级别,可以设置的有: fatal/alert/crit/error/warn/notice/info/debug/trace/notset/unknown.各个日志级别越来越弱,fatal表示只有fatal的日志才输出,alert表示只有fatal/alert级别的日志才输出,其它一次类推,unknown表输出所有级别的日志信息。
l maxsize的值为进行日志文件回滚时日志文件的大小,单位是字节。
l maxnum的值表示至多记录多少个日志文件,然后重新回滚到第一个日志文件名。
l prefix的值是生成日志文件的文件名前缀。
l reread的值表示是否输出每条日志信息时都重新读取配置文件,这个在不希望关闭程序动态改变输出日志级别时特别有用,0表示否,1表示是。
l log4crc配置文件内容:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!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" appender="aname"/>
<rollingpolicy name="a_policy_name" type="sizewin" maxsize="1048576" maxnum="15" />
<appender name="aname" type="rollingfile" logdir="." prefix="log" layout="dated_threadid" rollingpolicy="a_policy_name" />
<appender name="stdout" type="stream" layout="dated_threadid"/>
<appender name="stderr" type="stream" layout="dated"/>
<appender name="syslog" type="syslog" layout="basic"/>
</log4c>
5. 性能测试
5.1. C语言方式性能测试
1ms可以记录60条如下的语句:
20080215 11:22:27.859 ThreadID:888 ERROR - test_usinglog4c.c(31) Hello world! My age is 8, and my name is 黄道义!
统计性能:平均1S可记录3048条日记。
每条日记花费时间是:3.28e-4 (0.000328S)
5.2. C++语言方面性能测试
1ms输出了7个日志。
6. 空间占用
6.1. C语言空间占用
l Debug版本
日志库本身带来生成可执行文件的大小增加140K,其它就是记录日志的宏定义的代码导致文件大小的增加了。
l Release版本
日志库本身带来生成可执行文件的大小增加70K,其它就是记录日志的宏定义的代码导致文件大小的增加了。
6.2. C++语言空间占用
l Debug版本
日志库本身带来生成可执行文件的大小增加212K,其它就是记录日志的宏定义的代码导致文件大小的增加了。
l Release版本
日志库本身带来生成可执行文件的大小增加105K,其它就是记录日志的宏定义的代码导致文件大小的增加了。
7. Linux平台编译注意事项
本日志头文件在linux平台下编译测试通过,Linux平台下要:
n 定义如下宏定义:
PTHREAD/HAVE_PTHREAD_H/HAVE_SHM
C语言方式日志宏定义开关: LOG4C_ENABLE
C++语言方式日志宏定义开关: LOG_USELOG4CXX
n 链接thread线程库
8. 参考资料及备注
【1】 Log4c开源源码和相应文档。
【2】 Log4c网上版本打印出来的时间值是格林尼治时间,不是本地时间,进行了修正。
【3】 Log4c的日志输出格式中没有线程ID,加上了线程ID。
【4】 Log4c的日志中没有输出文件名和行号,加上了文件名和行号。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/wenhm/archive/2009/08/14/4445955.aspx