Log4cpp介绍及使用

Log4cpp介绍及使用

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

  Log4cpp是一个开源的C++类库,它提供了在C++程序中使用日志和跟踪调试的功能。使用log4cpp,能够非常便利地将日志或者跟踪调试信息写入字符流、内存字符串队列、文件、回滚文件、调试器、Windows日志、本地syslog和远程syslogserver中。

1、Log4cpp简单介绍

  Log4cpp是个基于LGPL的开源项目,移植自Java的日志处理跟踪项目log4j,并保持了API上的一致。其类似的支持库还包含Java(log4j),C++(log4cpp、log4cplus),C(log4c),python(log4p)等。

  Log4cpp有例如以下长处:

•提供了可扩展的多种日志记录方式;

•提供了NDC(嵌套诊断上下文),可用于多线程、多场景的跟踪调试;

•提供了完整的日志动态优先级控制,可随时调整须要记录的日志优先级;

•可通过配置文件完毕全部配置并动态载入;

•性能优秀,内存占用小,经过编译后的log4cpp.dll大小仅有160kb;

•代码级的平台无关性,Log4cpp源码经过编译后,适用于大多数主流的操作系统和开发工具;

•概念清晰,学习和使用方便,熟练程序猿一天之内就可以非常好地应用log4cpp进行开发。

2、资源及使用

  2.1资源链接

  Log4cpp的主页为:http://sourceforge.net/projects/log4cpp/

  下载版本号0.3.5rc3,这个版本号眼下是最稳定的,版本号1.0在VC中表现不稳定。下载后的包名字为:log4cpp-0.3.5rc3.tar.gz(源码包)和log4cpp-docs-0.3.5rc3.tar.gz(文档压缩包)。

  2.2在VC6中编译Log4cpp

  进入D:\log4cpp-0.3.5rc3\msvc6文件夹,打开VC6的工作区msvc6.dsw,将当中的project都删除,仅仅保留log4cpp和log4cppDLL两个project。分别编译它们的Debug和Release版本号。

  在VC6中编译Log4cpp会报错,事实上仅仅有一个错误,即不能在头文件里定义变量,同一时候给变量赋默认值。改动方法例如以下:将头文件Priority.hh中的这一行:

static const int MESSAGE_SIZE = 8;

改为:

staticconst intMESSAGE_SIZE;

并在Priority.cpp中的全部include语句后加上:

constint log4cpp::Priority::MESSAGE_SIZE =8;

  编译链接成功后会得到log4cppD.dll、log4cppD.lib(Debug版的dll和lib文件)和log4cpp.dll、log4cpp.lib(Release版的dll和lib文件)。新建文件夹D:\log4cpp-0.3.5rc3\lib,将以上四个文件复制到该文件夹下。

  在VC中加入�设置lib和include路径。

  将D:\log4cpp-0.3.5rc3\lib添�系统的Path路径中。

  2.3样例程序

  本文包括了大量的样例程序,这些程序被组织为多个project,并放入了一个名为WxbLogDsw的VC工作区。全部代码被打包为一个名为WxbLogDsw.rar的压缩文件,解压后可在VC6以上版本号中打开此project并进行编译执行。

 

3、Log4cpp演示样例

  让我们从一个简单的样例開始,该样例将两条日志信息写入字符串流,该流会在标准控制台cout上输出,项目的名称是HelloLog4Cpp:

#include<iostream>

#include”log4cpp/Category.hh”

#include”log4cpp/OstreamAppender.hh”

#include”log4cpp/BasicLayout.hh”

#include”log4cpp/Priority.hh”

using namespace std;

int main(int argc,char* argv[])

{

log4cpp::OstreamAppender* osAppender =newlog4cpp::OstreamAppender(“osAppender”,&cout);

 osAppender->setLayout(newlog4cpp::BasicLayout());

 

 log4cpp::Category& root =log4cpp::Category::getRoot();

 root.addAppender(osAppender);

root.setPriority(log4cpp::Priority::DEBUG);

root.error(“Hello log4cpp in aError Message!”);

root.warn(“Hello log4cpp in aWarning Message!”);

log4cpp::Category::shutdown();    

return 0;

}

  要顺利编译执行还有两个地方须要设置,其一是引入的库中加上log4cppD.lib(debug版dll库的引入文件);其二是将C/C++的CodeGeneration中的Use Runtimelibrary设置为“DebugMultithreaded DLL”。

  设置完毕后编译执行结果例如以下:

1248337987ERROR : Hello log4cppin a Error Message!
1248337987 WARN : Hello log4cppin a Warning Message!

  以上两条日志格式非常简陋,要设置合乎心意的日志格式,请參考兴许的PatternLayout章节。

4、Log4cpp概念

  Log4cpp中的概念继承自log4j,最重要的是Category(种类)、Appender(附加目的地)和Layout(布局)三个概念,此外还有Priority(优先级)和NDC(嵌套的诊断上下文)等。

  简言之,Category负责向日志中写入信息,Appender负责指定日志的目的地,Layout负责设定日志的格式,Priority被用来指定Category的优先级和日志的优先级, NDC则是一种用来区分不同场景中交替出现的日志的手段。

  Log4cpp记录日志的原理例如以下:每一个Category都有一个优先级,该优先级能够由setPriority方法设置,或者从其父Category中继承而来。每条日志也有一个优先级,当Category记录该条日志时,若日志优先级高于Category的优先级时,该日志被记录,否则被忽略。系统中默认的优先级等级例如以下:

        typedefenum {

EMERG  = 0,

FATAL  = 0,

ALERT  = 100,

CRIT   = 200,

ERROR  = 300,

WARN   = 400,

NOTICE =500,

INFO   = 600,

DEBUG  = 700,

NOTSET =800

}PriorityLevel;

  注意:取值越小,优先级越高。比如一个Category的优先级为101,则全部EMERG、FATAL、ALERT日志都能够记录下来,而其它则不能。

  Category、Appender和Layout三者的关系例如以下:系统中能够有多个Category,它们都是继承自同一个根,每一个Category负责记录自己的日志;每一个Category能够加入�多个Appender,每一个Appender指定了一个日志的目的地,比如文件、字符流或者Windows日志,当Category记录一条日志时,该日志被写入全部附加到此Category的Appender;每一个Append都包括一个Layout,该Layout定义了这个Appender上日志的格式。

  如今重温前面的HelloWorld程序,能够发现其流程例如以下:

    1. 创建一个Appender,并指定其包括的Layout;

2. 从系统中得到Category的根,将Appender加入�到该Category中;

3. 设置Category的优先级;

4. 记录日志;

5. 关闭Category。

  以下,我们依照Layout、Appender、Category、NDC的顺序来依次介绍这些概念并给出样例。

4.1Layout(布局)

layout类控制输出日志消息的显示样式(看起来像什么)。log4cpp当前提供下面layout格式:

     log4cpp::BasicLayout         // 以“时间戳 优先级(priority,下文介绍)
                                  // 类别(category,下文介绍)

                                //NDC标签(nested diagnostic contexts 下文介绍): 日志信息”。

                                 //如:1056638652 INFO main : This is someinfo

log4cpp::PatternLayout     // 让用户依据类似于C 语言 printf 函数的转换模式

                                 //来指定输出格式。格式定义见代码附带文档。

log4cpp::SimpleLayout     // 以“优先级(priority) – 日志信息”格式显示。

  首先回想一下HelloWorld的日志格式,它使用了最简单的BasicLayout:

1248337987 ERROR  : Hello log4cppin a Error Message!

1248337987 WARN  : Hello log4cppin a Warning Message!

  上面的日志格式还能够,但显然不是很多程序猿心中理想的格式,很多人理想的格式应该是这种:

2009-07-24 15:59:55,703:INFO infoCategory : system isrunning

2009-07-24 15:59:55,703:WARN infoCategory : system has a warning

2009-07-24 15:59:55,703:ERROR infoCategory : system has a error, can’t find a file

2009-07-24 15:59:55,718:FATAL infoCategory : system has a fatal error, must beshutdown

2009-07-24 15:59:55,718:INFO infoCategory : system shutdown, you can find some informationin system log

  要获得上面的格式,必须使用比BasicLayout复杂的PatternLayout,并且要花一个小时来熟悉一下PatternLayout的格式定义方式,假设你觉得值得的话。

  4.1.1PatternLayout

  在介绍PatternLayout曾经,首先来看看log4cpp中全部的Layout子类(Layout本身是个虚类),一共三个:BasicLayout、PatternLayout和SimpleLayout,当中SimapleLayout并不建议使用,而BaiscLayout过于简单,因此假设程序猿不自己扩展Layout的话,就仅仅能使用PatternLayout了,值得庆幸的是,PatternLayout还是比較好用的。

  PatternLayout使用setConversionPattern函数来设置日志的输出格式。该函数的声明例如以下:

void log4cpp::PatternLayout::setConversionPattern  (conststd::string&  conversionPattern)  throw(ConfigureFailure) [virtual]

  当中參数类型为std::string,类似于C语言中的printf,使用格式化字符串来描写叙述输出格式,其详细含义例如以下:

%c category;

%d 日期;日期能够进一步的设置格式,用花括号包围,比如%d{%H:%M:%S,%l} 或者 %d{%d %m %Y%H:%M:%S,%l}。假设不设置详细日期格式,则例如以下默认格式被使用“Wed Jan 02 02:03:55 1980”。日期的格式符号与ANSI C函数strftime中的一致。但添加�了一个格式符号%l,表示毫秒,占三个十进制位。

%m 消息;

%n 换行符,会依据平台的不同而不同,但对于用户透明;

%p 优先级;

%r 自从layout被创建后的毫秒数;

%R 从1970年1月1日0时開始到眼下为止的秒数;

%u 进程開始到眼下为止的时钟周期数;

%x NDC。

  因此,要得到上述的理想格式,能够将setConversionPattern的參数设置为“%d: %p %c %x:%m%n”,其详细含义是“时间:优先级 Category NDC: 消息换行”。使用PatternLayout的样例程序例如以下,项目名称是LayoutExam:

#include<iostream>

#include<log4cpp/Category.hh>

#include<log4cpp/OstreamAppender.hh>

#include<log4cpp/Priority.hh>

#include<log4cpp/PatternLayout.hh>

using namespace std;

int main(int argc,char* argv[])

{

log4cpp::OstreamAppender* osAppender = new log4cpp::OstreamAppender(“osAppender”,&cout);

log4cpp::PatternLayout* pLayout = new log4cpp::PatternLayout();

pLayout->setConversionPattern(“%d: %p %c %x: %m%n”);

osAppender->setLayout(pLayout);

 

log4cpp::Category& root =log4cpp::Category::getRoot();

log4cpp::Category& infoCategory =root.getInstance(“infoCategory”);

infoCategory.addAppender(osAppender);

infoCategory.setPriority(log4cpp::Priority::INFO);

 

infoCategory.info(“system isrunning”);

infoCategory.warn(“system has awarning”);

infoCategory.error(“system hasa error, can’t find a file”);

infoCategory.fatal(“system hasa fatal error,must be shutdown”);

infoCategory.info(“systemshutdown,you can find some information in systemlog”);

log4cpp::Category::shutdown();

return 0;

}

其执行结果即例如以下所看到的:

2009-07-2415:59:55,703: INFO infoCategory : system is running

2009-07-2415:59:55,703: WARN infoCategory : system has a warning

2009-07-2415:59:55,703: ERROR infoCategory : system has a error, can’t find afile

2009-07-2415:59:55,718: FATAL infoCategory : system has a fatal error, mustbe shutdown

2009-07-2415:59:55,718: INFO infoCategory : system shutdown, you can findsome information in system log

4.2 Appender

  笔者觉得Appender是log4cpp中最精彩的一个部分。我细致阅读了大部分Appender的源码并对设计者感到很仰慕。

  Log4cpp中全部可直接使用的Appender列表例如以下:

Ø log4cpp::IdsaAppender                        // 发送到IDS或者

Ø log4cpp::FileAppender                         // 输出到文件

Ø log4cpp::RollingFileAppender            // 输出到回卷文件,即当文件到达某个大小后回卷

Ø log4cpp::OstreamAppender               // 输出到一个ostream类

Ø log4cpp::RemoteSyslogAppender             // 输出到远程syslogserver

Ø log4cpp::StringQueueAppender       // 内存队列

Ø log4cpp::SyslogAppender                    // 本地syslog

Ø log4cpp::Win32DebugAppender      // 发送到缺省系统调试器

Ø log4cpp::NTEventLogAppender        // 发送到win事件日志

  当中SyslogAppender和RemoteSyslogAppender须要与Syslog配合使用,因此这里不介绍。顺便提一句,Syslog是类Unix系统的一个核心服务,用来提供日志服务,在Windows系统中并没有直接提供支持,当然能够用相关工具()提供Windows系统中的syslog服务。

  IdsaAppender的功能是将日志写入Idsa服务,这里也不介绍。因此主要介绍下面Appender:

log4cpp::FileAppender                      // 输出到文件

log4cpp::RollingFileAppender         // 输出到回卷文件,即当文件到达某个大小后回卷

log4cpp::OstreamAppender           // 输出到一个ostream类

log4cpp::StringQueueAppender             // 内存队列

log4cpp::Win32DebugAppender            // 发送到缺省系统调试器

log4cpp::NTEventLogAppender      //发送到win事件日志

  4.2.1OstreamAppender

  在我刚刚学习C/C++编程时,一位老师告诉我,假设没有好用的调试工具,就在代码中添�printf语句,将调试打印信息出来(当时在linux以下,确实没有什么易用的c++调试工具)。如今有了OstreamAppender,一切都好办了,它能够将日志记入一个流,假设该流恰好是cout,则会在标准控制台上输出。比printf优越的是,除了输出消息外,还能够轻松的输出时间、时钟数、优先级等大量实用信息。

  OstreamAppender的使用很easy,在前面的HelloWorld程序中已经见过,创建一个OstreamAppender的详细方法例如以下:

log4cpp::OstreamAppender* osAppender = newlog4cpp::OstreamAppender(“osAppender”, &cout);

  第一个參数指定OstreamAppender的名称,第二个參数指定它关联的流的指针。

  4.2.2StringQueueAppender

  后来一位高手又告诉我“在调试多线程程序时,不能任意使用printf”。由于printf导致IO中断,会使得本线程挂起,其花费的时间比一条普通指令多数千倍,若多个线程同一时候执行,则严重干扰了线程间的执行方式。所以调试多线程程序时,最好是将全部调试信息按顺序记入内存中,程序结束时依次打印出来。为此当时我们还写了一个小工具,没想到时隔多年,我碰上了StringQueueAppender。

我非常怀疑StringQueueAppender被设计出来就是用于记录多线程程序或者实时程序的日志,尽管log4cpp的文档中并没有明白指出这一点。StringQueueAppender的功能是将日志记录到一个字符串队列中,该字符串队列使用了STL中的两个容器,即字符串容器std::string和队列容器std::queue,详细例如以下:

std::queue<std::string> _queue;

  _queue变量是StringQueueAppender类中用于详细存储日志的内存队列。StringQueueAppender的用法与OstreamAppender类似,其创建函数仅仅接收一个參数“名称”,记录完毕后须要程序猿自己从队列中取出每条日志,样例程序StringQueueAppenderExam例如以下:

#include<iostream>

#include<log4cpp/Category.hh>

#include<log4cpp/OstreamAppender.hh>

#include<log4cpp/BasicLayout.hh>

#include<log4cpp/Priority.hh>

#include<log4cpp/StringQueueAppender.hh>

using namespacestd;

int main(int argc,char* argv[])

{

log4cpp::StringQueueAppender* strQAppender = newlog4cpp::StringQueueAppender(“strQAppender”);

strQAppender->setLayout(newlog4cpp::BasicLayout());

 

log4cpp::Category& root =log4cpp::Category::getRoot();

root.addAppender(strQAppender);

root.setPriority(log4cpp::Priority::DEBUG);

root.error(“Hello log4cpp in a Error Message!”);

root.warn(“Hello log4cpp in a WarningMessage!”);

cout<<“Get message from MemoryQueue!”<<endl;

cout<<“——————————————-“<<endl;

queue<string>& myStrQ =strQAppender->getQueue();

while(!myStrQ.empty())

{

cout<<myStrQ.front();

myStrQ.pop();

}

log4cpp::Category::shutdown();   

return 0;

}

  程序输出为:

Getmessage from Memory Queue!

——————————————-

1248839389 ERROR  : Hellolog4cpp in a Error Message!

1248839389 WARN  : Hellolog4cpp in a Warning Message!

 

  4.2.3FileAppender和RollingFileAppender

  FileAppender和RollingFileAppender是log4cpp中最经常使用的两个Appender,其功能是将日志写入文件里。它们之间唯一的差别就是前者会一直在文件里记录日志(直到操作系统承受不了为止),而后者会在文件长度到达指定值时循环记录日志,文件长度不会超过指定值(默认的指定值是10M byte)。

  FileAppender的创建函数例如以下:

          

          FileAppender(conststd::string& name, conststd::string& fileName, bool append = true, mode_tmode = 00644);

  一般仅使用前两个參数,即“名称”和“日志文件名称”。第三个參数指示是否在日志文件后继续记入日志,还是清空原日志文件再记录。第四个參数说明文件的打开方式。

  RollingFileAppender的创建函数例如以下:

RollingFileAppender(const std::string&name,  const std::string&fileName,  

                       size_tmaxFileSize =10*1024*1024,  unsigned intmaxBackupIndex = 1,

                       boolappend = true,  mode_t mode =00644);

  它与FileAppender的创建函数非常类似,可是多了两个參数:maxFileSize指出了回滚文件的最大值;maxBackupIndex指出了回滚文件所用的备份文件的最大个数。所谓备份文件,是用来保存回滚文件里由于空间不足未能记录的日志,备份文件的大小仅比回滚文件的最大值大1kb。所以假设maxBackupIndex取值为3,则回滚文件(假设其名称是rollwxb.log,大小为100kb)会有三个备份文件,其名称各自是rollwxb.log.1,rollwxb.log.2和rollwxb.log.3,大小为101kb。另外要注意:假设maxBackupIndex取值为0或者小于0,则回滚文件功能会失效,其表现如同FileAppender一样,不会有大小的限制。这或许是一个bug。

  样例程序FileAppenderExam例如以下:

#include <iostream>

#include <log4cpp/Category.hh>

#include <log4cpp/Appender.hh>

#include <log4cpp/FileAppender.hh>

#include <log4cpp/Priority.hh>

#include <log4cpp/PatternLayout.hh>

#include <log4cpp/RollingFileAppender.hh>

using namespace std;

 

int main(int argc, char* argv[])

{

log4cpp::PatternLayout* pLayout1 = newlog4cpp::PatternLayout();

pLayout1->setConversionPattern(“%d: %p %c%x: %m%n”);

 

log4cpp::PatternLayout* pLayout2 = newlog4cpp::PatternLayout();

pLayout2->setConversionPattern(“%d: %p %c%x: %m%n”);

 

log4cpp::Appender* fileAppender = newlog4cpp::FileAppender(“fileAppender”,”wxb.log”);

fileAppender->setLayout(pLayout1);

 

log4cpp::RollingFileAppender* rollfileAppender = newlog4cpp::RollingFileAppender( “rollfileAppender”,”rollwxb.log”,5*1024,1);

rollfileAppender->setLayout(pLayout2);

 

log4cpp::Category& root =log4cpp::Category::getRoot().getInstance(“RootName”);

root.addAppender(fileAppender);

root.addAppender(rollfileAppender);

root.setPriority(log4cpp::Priority::DEBUG);

for (int i = 0; i < 100; i++)

{

 string strError;

ostringstream oss;

oss<<i<<“:RootError Message!”;

strError = oss.str();

root.error(strError);

}

log4cpp::Category::shutdown();

return 0;

}

  程序执行后会产生两个日志文件wxb.log和rollwxb.log,以及一个备份文件rollwxb.log.1。wxb.log的大小为7kb,记录了全部100条日志;rollwxb.log大小为2kb,记录了最新的22条日志;rollwxb.log.1大小为6kb,记录了旧的78条日志。

  4.2.4Win32DebugAppender

  Win32DebugAppender是一个用于调试的Appender,其功能是向Windows的调试器中写入日志,眼下支持MSVC和Borland中的调试器。创建Win32DebugAppender仅须要一个參数“名称”,其使用很easy,以下是样例代码DebugAppenderExam:

#include <iostream>

#include <log4cpp/Category.hh>

#include <log4cpp/Appender.hh>

#include <log4cpp/Win32DebugAppender.hh>

#include <log4cpp/Priority.hh>

#include <log4cpp/PatternLayout.hh>

using namespace std;

int main(int argc, char* argv[])

{

log4cpp::PatternLayout* pLayout1 = newlog4cpp::PatternLayout();

pLayout1->setConversionPattern(“%d: %p %c%x: %m%n”);

 

log4cpp::Appender* debugAppender = newlog4cpp::Win32DebugAppender(“debugAppender”);

debugAppender->setLayout(pLayout1);

 

log4cpp::Category& root =log4cpp::Category::getRoot().getInstance(“RootName”);

root.addAppender(debugAppender);

root.setPriority(log4cpp::Priority::DEBUG);

 

root.error(“Root Error Message!”);

root.warn(“Root Warning Message!”);

 

log4cpp::Category::shutdown();

return 0;

}

  在VC6中调试该代码会得到例如以下图所看到的的调试信息,注意最下方的两行调试信息:

 

  4.2.5NTEventLogAppender

   该Appender能够将日志发送到windows的日志,在执行程序后能够打开windows的计算机管理->系统工具->事件查看器->应用程序,能够看到下图,注意图中第一行和第二行的两个日志。

 

  样例程序NTAppenderExam例如以下:

#include<iostream>

#include<log4cpp/Category.hh>

#include<log4cpp/Appender.hh>

#include<log4cpp/NTEventLogAppender.hh>

#include<log4cpp/Priority.hh>

#include<log4cpp/PatternLayout.hh>

using namespace std;

 

int main(int argc, char* argv[])

{

log4cpp::PatternLayout* pLayout1 = newlog4cpp::PatternLayout();

pLayout1->setConversionPattern(“%d: %p %c%x: %m%n”);

 

log4cpp::Appender* ntAppender = newlog4cpp::NTEventLogAppender(“debugAppender”,”wxb_ntlog”);

ntAppender->setLayout(pLayout1);

 

log4cpp::Category& root =log4cpp::Category::getRoot().getInstance(“RootName”);

root.addAppender(ntAppender);

 

root.setPriority(log4cpp::Priority::DEBUG);

root.error(“Root Error Message!”);

root.warn(“Root Warning Message!”);

 

log4cpp::Category::shutdown();

return 0;

}

4.3 Category

  Log4cpp中有一个总是可用并实例化好的Category,即根Category。使用log4cpp::Category::getRoot()能够得到根Category。在大多数情况下,一个应用程序仅仅须要一个日志种类(Category),可是有时也会用到多个Category,此时能够使用根Category的getInstance方法来得到子Category。不同的子Category用于不同的场合。一个简单的样例CategoryExam例如以下所看到的:

#include <iostream>

#include <log4cpp/Category.hh>

#include <log4cpp/OstreamAppender.hh>

#include <log4cpp/FileAppender.hh>

#include <log4cpp/BasicLayout.hh>

#include <log4cpp/Priority.hh>

using namespace std;

int main(int argc, char* argv[])

{

log4cpp::OstreamAppender*osAppender1 = new log4cpp::OstreamAppender(“osAppender1”,&cout);

osAppender1->setLayout(newlog4cpp::BasicLayout());

 

log4cpp::OstreamAppender*osAppender2 = new log4cpp::OstreamAppender(“osAppender2”,&cout);

osAppender2->setLayout(newlog4cpp::BasicLayout());

 

log4cpp::Category& root =log4cpp::Category::getRoot();

root.setPriority(log4cpp::Priority::DEBUG);

 

log4cpp::Category& sub1 =root.getInstance(“sub1”);

sub1.addAppender(osAppender1);

sub1.setPriority(log4cpp::Priority::DEBUG);

sub1.error(“suberror”);

log4cpp::Category& sub2 =root.getInstance(“sub2”);

sub2.addAppender(osAppender2);

sub2.setPriority(101);

sub2.warn(“sub2warning”);

sub2.fatal(“sub2fatal”);

sub2.alert(“sub2alert”);

sub2.crit(“sub2crit”);

log4cpp::Category::shutdown();

return 0;

}

执行结果例如以下:

1248869982 ERRORsub1 : sub error

1248869982 FATALsub2 : sub2 fatal

1248869982 ALERTsub2 : sub2 alert

  这个样例中共同拥有三个Category,各自是根、sub1和sub2,当中sub1记录了一条日志,sub2记录了两条日志。Sub2另外两个日志因为优先级不够未能记录。

4.4 NDC

  NDC是nested DiagnosticContext的缩写,意思是“嵌套的诊断上下文”。NDC是一种用来区分不同源码中交替出现的日志的手段。当一个服务端程序同一时候记录好几个并行客户时,输出的日志会混杂在一起难以区分。但假设不同上下文的日志入口拥有一个特定的标识,则能够解决问题。NDC就是在这样的情况下发挥作用。注意NDC是以线程为基础的,每一个线程拥有一个NDC,每一个NDC的操作仅对运行该操作的线程有效。

  NDC的几个实用的方法是:push、pop、get和clear。注意它们都是静态函数:

  Push能够让当前线程进入一个NDC,假设该NDC不存在,则依据push的參数创建一个NDC并进入;假设再调用一次push,则进入子NDC;

  Pop能够让当前线程从上一级NDC中退出,可是一次仅仅能退出一级。

  Clear能够让当前线程从全部嵌套的NDC中退出。

  Get能够得到当前NDC的名字,假设有嵌套,则不同级别之间的名字用空格隔开。

  一个简单的样例NDCExam例如以下:

#include<iostream>

#include<log4cpp/NDC.hh>

using namespacelog4cpp;

int main(int argc,char** argv)

{

std::cout<< “1.empty NDC: ” <<NDC::get()<< std::endl;

NDC::push(“context1”);

std::cout<< “2.push context1: ” <<NDC::get()<< std::endl;

NDC::push(“context2”);

std::cout<< “3.push context2: ” <<NDC::get()<< std::endl;

NDC::push(“context3”);

std::cout<< “4.push context3: ” <<NDC::get()<< std::endl;

std::cout<< “5.get depth: ” <<NDC::getDepth() <<std::endl;

std::cout<< “6.pop: ” << NDC::pop()<< std::endl;

std::cout<< “7.after pop:”<<NDC::get()<<std::endl;

 

NDC::clear();

std::cout<< “8.clear: ” << NDC::get() <<std::endl;

return 0;

}

  该样例来自log4cpp的样例程序,我做了简单的改动。在记录日志的时候,能够从NDC中得知当前线程的嵌套关系。

5、Log4cpp的自己主动内存管理

   8.1 项目的多线程设置

   VC中必须将项目设置为Debug MultiThreaded DLL,总之这个设置必须与你使用的Log4cpp库一致。假设你使用的是Release版本号的log4cpp.dll,则应该设置为MultiThreaded DLL。

  否则在程序结束时会报错,报错处的调用堆栈为:

log4cpp::BasicLayout::`vector deleting destructor'(unsignedint 1) + 122 bytes

log4cpp::LayoutAppender::~LayoutAppender() line 21 + 35bytes

log4cpp::OstreamAppender::~OstreamAppender() line 28 + 15bytes

log4cpp::OstreamAppender::`vector deletingdestructor'(unsigned int 1) + 103 bytes

log4cpp::Category::removeAllAppenders() line 159 + 39bytes

log4cpp::HierarchyMaintainer::shutdown() line 101 + 27bytes

log4cpp::HierarchyMaintainer::~HierarchyMaintainer() line36

  8.2Log4cpp的内存对象管理

  或许读者已经注意到,在前面的全部代码中,log4cpp中全部动态分配的对象都没有手动释放。

  Log4cpp中new出来的Category、Appender和Layout都不须要手动释放,由于Log4cpp使用了一个内部类来管理这些对象。此类的名称是HierarchyMaintainer,它负责管理Category的继承关系,在程序结束时,HierarchyMaintainer会依次释放全部Category,而Category则会依次释放拥有的有效Appender,Appender则会释放全部附属的Layout。假设程序猿手动释放这些对象,则会造成内存报错。

  从以下的代码能够看出这个特征:

appender->setLayout(newlog4cpp::BasicLayout());

  这个new出来的BasicLayout根本就没有保存其指针,所以它仅仅能被log4cpp的内存管理类HierarchyMaintainer释放。

  了解到HierarchyMaintainer的内存管理方法后,程序猿在使用log4cpp时应该遵循下面几个使用原则:

Ø 不要手动释放Category、Appender和Layout;

Ø 同一个Appender不要添�多个Category,否则它会被释放多次从而导致程序崩溃;

Ø 同一个Layout不要附着到多个Appender上,否则也会被释放多次导致程序崩溃;

  以下这个简单的程序PointerErrorExam会造成经典的崩溃:

#include <iostream>

#include <log4cpp/Category.hh>

#include <log4cpp/OstreamAppender.hh>

#include <log4cpp/BasicLayout.hh>

#include <log4cpp/Priority.hh>

using namespace std;

 

int main(int argc, char* argv[])

{

 log4cpp::OstreamAppender* osAppender = newlog4cpp::OstreamAppender(“osAppender”, &cout);

 osAppender->setLayout(newlog4cpp::BasicLayout());

 

 log4cpp::Category& root =log4cpp::Category::getRoot();

 root.setPriority(log4cpp::Priority::DEBUG);

 

 log4cpp::Category& sub1 =root.getInstance(“sub1”);

 sub1.addAppender(osAppender);

 sub1.error(“sub1 error”);

 

 log4cpp::Category& sub2 =root.getInstance(“sub2”);

 sub2.addAppender(osAppender);

 sub2.warn(“sub2 warning”);

 

 log4cpp::Category::shutdown();

 return 0;

}

 执行后出现对话框:

PointerErrorExam.exe 遇到问题须要关闭。我们对此引起的不便表示抱歉。

  其原因就是osAppender被同一时候添�了sub1和sub2这两个Category。

  8.3log4cpp::Category::shutdown()

  在不使用log4cpp时可调用log4cpp::Category::shutdown(),其功能如同HierarchyMaintainer的内存清理。但假设不手动调用,在程序结束时HierarchyMaintainer会调用Category的析构函数来释放全部Appender。

6、利用配置文件定制日志

如同log4j一样,log4cpp也能够读取配置文件来定制Category、Appender和Layout对象。其配置文件格式基本类似于log4j,一个简单的配置文件log4cpp.ini样例例如以下:

   #log4cpp配置文件

#定义Root category的属性

log4cpp.rootCategory=DEBUG, RootLog

 

#定义RootLog属性

log4cpp.appender.RootLog=ConsoleAppender

log4cpp.appender.RootLog.layout=PatternLayout

log4cpp.appender.RootLog.layout.ConversionPattern=%d [%p] -%m%n

 

#定义sample category的属性

log4cpp.category.sample=DEBUG, sample

 

#定义sample属性

log4cpp.appender.sample=FileAppender

log4cpp.appender.sample.fileName=sample.log

log4cpp.appender.sample.layout=PatternLayout

log4cpp.appender.sample.layout.ConversionPattern=%d [%p] -%m%n

 

#定义sample.soncategory的属性

log4cpp.category.sample.son=DEBUG, son

 

#定义son的属性

log4cpp.appender.son=FileAppender

log4cpp.appender.son.fileName=son.log

log4cpp.appender.son.layout=PatternLayout

log4cpp.appender.son.layout.ConversionPattern=%d[%p] – %m%n

 

#定义sample.daughtercategory的属性

log4cpp.category.sample.daughter=DEBUG,daughter

 

#定义daughter属性

log4cpp.appender.daughter=FileAppender

log4cpp.appender.daughter.fileName=daughter.log

log4cpp.appender.daughter.layout=PatternLayout

log4cpp.appender.daughter.layout.ConversionPattern=%d [%p]- %m%n

相应category 和 appender 的配置方式,能够发现

category 是”log4cpp.category.” + “categoryname”

category 名字能够用”.”分隔,以标识包括关系

appender 是”log4cpp.appender.” + “appendername”

appender 名字 不能用 “.” 分隔,即是说 appender 是没有包括关系的

读取配置文件要依赖PropertyConfigurator和SimpleConfigurator类。这里仅介绍PropertyConfigurator,其用法代码ConfigFileExam所看到的(该代码来自《便利的开发工具-log4cpp高速使用指南》一文):

#include<iostream>

#include<log4cpp/Category.hh>

#include<log4cpp/PropertyConfigurator.hh>

int main(int argc,char* argv[])

{

try

{

log4cpp::PropertyConfigurator::configure(“./log4cpp.conf”);

}

catch(log4cpp::ConfigureFailure& f)

{

std::cout<< “Configure Problem “<< f.what()<< std::endl;

return -1;

}

log4cpp::Category& root =log4cpp::Category::getRoot();

log4cpp::Category& sub1 =log4cpp::Category::getInstance(std::string(“sub1”));

log4cpp::Category& sub3 =log4cpp::Category::getInstance(std::string(“sub1.sub2”));

sub1.info(“This is someinfo”);

sub1.alert(“Awarning”);

// sub3 only have A2 appender.

sub3.debug(“This debug messagewill fail to write”);

sub3.alert(“All hands abandonship”);

sub3.critStream() <<“This will show up<< as “<< 1 <<” critical message”<<log4cpp::CategoryStream::ENDLINE;

sub3<<log4cpp::Priority::ERROR<<“And this will be anerror”  <<log4cpp::CategoryStream::ENDLINE;

sub3.log(log4cpp::Priority::WARN, “This will be a logged warning”);

return0;

}

  该程序首先读入了配置文件log4cpp.conf,从中得到了全部Category、Appender和Layout的优先级和相互附属关系,然后输出了一些日志,其执行结果例如以下:

1248875649 INFO sub1 : This is some info

1248875649 ALERT sub1 : A warning

The message All hands abandon ship at time 2009-07-2921:54:09,515

1248875649 ALERT sub1.sub2 : All hands abandonship

The message This will show up<< as 1 critical message at time2009-07-29 21:54:09,531

1248875649 CRIT sub1.sub2 : This will show up<< as 1 critical message

The message And this will be an error at time 2009-07-2921:54:09,531

1248875649 ERROR sub1.sub2 : And this will be anerror

7、DLL的版本号问题

  若在VC6中使用Log4cpp的DLL,则必须使用VC6编译链接生成的DLL,不能使用MSVS2008中生成的DLL,反之也是一样。否则会在执行时报错。

问题:因为log4cpp-0.3.5rc3仅提供了vc6的project文件,因此,使用vs2005打开后,须要进行转换。可是转换后,不能正确编译,提示Custom Build Step时出现了错误。

分 析:由于log4cpp在生成NTEventLogAppender.dll时,须要连接NTEventLogCategories.mc文件。所以,项目设置了自己定义的生成步骤去生成NTEventLogAppender.dll。但从vc6的project文件转换时,这些步骤却没有正确的转换过来。从而出现上述问题。

解决方法:又一次填写Custom BuildStep项。当中,CommandLine填写下面内容:

if not exist $(OutDir) md$(OutDir)

“mc.exe” -h $(OutDir) -r $(OutDir)$(ProjectDir)..\$(InputName).mc

“RC.exe” -r -fo$(OutDir)\$(InputName).res $(OutDir)\$(InputName).rc

“link.exe” /MACHINE:IX86 -dll-noentry -out:$(OutDir)\NTEventLogAppender.dll$(OutDir)\$(InputName).res

适用范围:log4cpp项目、log4cppDLL项目的Debug和Release配置。同一时候,该方法适用于vs2003(vc7.1)。

问题:log4cppDLL项目编译时会报8个连接错误,提示符号std::_Tree找不到

解决方式:

将include\log4cpp\FactoryParams.hh文件里的

const_iterator find(conststd::string& t) const;

改动为:

const_iterator find(conststd::string& t) const { return storage_.find(t);}

后又一次编译问题:

log4cppDLL项目编译时会报1个连接错误,提示符号log4cpp::localtime找不到

解决方式:

将src\localtime.cpp文件加入�到项目中又一次编译

8、小结

  Log4cpp是一个小巧的c++库,易于上手,使用方便,不依赖其它库,具有跨平台性,并可与log4j、log4c、log4p等语言族共享其概念与用法。实在是进行日志记录、程序调试的利器。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/118777.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 使用哈夫曼树实现文本编码、解码

    使用哈夫曼树实现文本编码、解码使用二叉树存储结构的链表,进行构造二叉树,对指定字符串编码解码

    2022年5月3日
    51
  • clion 2021 永久激活码破解方法

    clion 2021 永久激活码破解方法,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月15日
    62
  • 利用Python+阿里云实现DDNS(动态域名解析)

    利用Python+阿里云实现DDNS(动态域名解析)引子我想大家应该都很熟悉DNS了,这回在DNS前面加了一个D又变成了什么呢?这个D就是Dynamic(动态),也就是说,按照传统,一个域名所对应的IP地址应该是定死的,而使用了DDNS后,域名所对应的IP是可以动态变化的。那这个有什么用呢?比如,在家里的路由器上连着一个raspberrypi(树莓派),上面跑着几个网站,我应该如和在外网环境下访问网站、登陆树莓派的SSH呢?还有,家里…

    2022年6月8日
    34
  • “栈”的典型应用—表达式求值(C语言实现)

    “栈”的典型应用—表达式求值(C语言实现)表达式求值是程序设计语言编译中的一个基本问题。它的实现就是对“栈”的典型应用。本文针对表达式求值使用的是最简单直观的算法“算符优先法”。我们都知道算术四则运算的运算规则是:先乘除,后加减。从左到右计算先算括号内,再算括号外表达式组成任何一个表达式都有操作数、运算符和界定符组成。操作数即可以是常量,也可以是被说明为变量或常量的标识符。运算符可以分为算术运算,关系运算和

    2022年6月15日
    34
  • idea如何删除一行_idea关联tomcat

    idea如何删除一行_idea关联tomcatideapreferencekeymanp搜索delete可以看到删除一行快捷键是command+delete按键

    2025年9月25日
    6
  • Redis事务详解

    Redis事务详解若对事务概念不清楚 请先阅读 彻底理解 MySQL 四种事务隔离级别 这篇文章 链接如下 彻底理解 MySQL 四种事务隔离级别 YaoYong BigData 的博客 CSDN 博客转入正题 结合关系型数据库的事务来看看 Redis 中事务有什么不同 Redis 事务是指将多条命令加入队列 一次批量执行多条命令 每条命令会按顺序执行 事务执行过程中不会受客户端传入的命令请求影响 Redis 事务的相关命令如下 MULTI 标识一个事务的开启 即开启事务 EXEC 执行事务中的所有命令 即提

    2025年10月14日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号