6.5 NTEventLogAppender
该Appender可以将日志发送到windows的日志,在运行程序后可以打开windows的计算机管理?系统工具?事件查看器?应用程序,可以看到下图,注意图中第一行和第二行的两个日志。
该Appender可以将日志发送到windows的日志,在运行程序后可以打开windows的计算机管理?系统工具?事件查看器?应用程序,可以看到下图,注意图中第一行和第二行的两个日志。
例子程序NTAppenderExam如下:
View Code
1 #include <iostream> 2 #include "log4cpp/Category.hh" 3 #include "log4cpp/Appender.hh" #include "log4cpp/NTEventLogAppender.hh" 4 #include "log4cpp/PatternLayout.hh" 5 #include "log4cpp/Priority.hh" 6 #pragma comment(lib,"log4cppD.lib") 7 //using namespace std; 8 int main(int arg,char* argv[]) { 9 //定义一个Appender,类型为输出Windows系统日志 10 log4cpp::Appender* ntEventApp = new log4cpp::NTEventLogAppender("ntEventApp","wxbnt.log"); 11 //定义一个Layout,类型为PatternLayout 12 log4cpp::PatternLayout* ptnLayout = new log4cpp::PatternLayout(); //设置该Layout的格式 13 ptnLayout->setConversionPattern("%d: %c %p %x:%m%n"); //将该Layout添加到定义的Appender中 14 ntEventApp->setLayout(ptnLayout); //定义一个Category的子类,名字为infoCategory 15 log4cpp::Category& infoCategory = log4cpp::Category::getRoot().getInstance("infoCategory"); //将上述的Appender添加到该Category中 16 infoCategory.setAppender(ntEventApp); //设置该Category的优先级 17 infoCategory.setPriority(log4cpp::Priority::DEBUG); //记录日志,将日志写入Windows系统日志 18 infoCategory.error("a error occurred!!"); 19 infoCategory.warn("a progam is in debug"); 20 infoCategory.shutdown(); 21 return 0; 22 }
结果:
右键事件属性
7、Category
Log4cpp中有一个总是可用并实例化好的Category,即根Category。使用log4cpp::Category::getRoot()可以得到根Category。在大多数情况下,一个应用程序只需要一个日志种类(Category),但是有时也会用到多个Category,此时可以使用根Category的getInstance方法来得到子Category。不同的子Category用于不同的场合。一个简单的例子CategoryExam如下所示:
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(new log4cpp::BasicLayout());
log4cpp::OstreamAppender* osAppender2 = new log4cpp::OstreamAppender("osAppender2", &cout);
osAppender2->setLayout(new log4cpp::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("sub error");
log4cpp::Category& sub2 = root.getInstance("sub2");
sub2.addAppender(osAppender2);
sub2.setPriority(101);
sub2.warn("sub2 warning");
sub2.fatal("sub2 fatal");
sub2.alert("sub2 alert");
sub2.crit("sub2 crit");
log4cpp::Category::shutdown();
return 0;
}
#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(new log4cpp::BasicLayout());
log4cpp::OstreamAppender* osAppender2 = new log4cpp::OstreamAppender("osAppender2", &cout);
osAppender2->setLayout(new log4cpp::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("sub error");
log4cpp::Category& sub2 = root.getInstance("sub2");
sub2.addAppender(osAppender2);
sub2.setPriority(101);
sub2.warn("sub2 warning");
sub2.fatal("sub2 fatal");
sub2.alert("sub2 alert");
sub2.crit("sub2 crit");
log4cpp::Category::shutdown();
return 0;
}
运行结果如下:
1248869982 ERROR sub1 : sub error
1248869982 FATAL sub2 : sub2 fatal
1248869982 ALERT sub2 : sub2 alert
1248869982 FATAL sub2 : sub2 fatal
1248869982 ALERT sub2 : sub2 alert
这个例子中共有三个Category,分别是根、sub1和sub2,其中sub1记录了一条日志,sub2记录了两条日志。Sub2另外两个日志由于优先级不够未能记录。
8、NDC
NDC是nested Diagnostic Context的缩写,意思是“嵌套的诊断上下文”。NDC是一种用来区分不同源代码中交替出现的日志的手段。当一个服务端程序同时记录好几个并行客户时,输出的日志会混杂在一起难以区分。但如果不同上下文的日志入口拥有一个特定的标识,则可以解决这个问题。NDC就是在这种情况下发挥作用。注意NDC是以线程为基础的,每个线程拥有一个NDC,每个NDC的操作仅对执行该操作的线程有效。
NDC的几个有用的方法是:push、pop、get和clear。注意它们都是静态函数:
Push可以让当前线程进入一个NDC,如果该NDC不存在,则根据push的参数创建一个NDC并进入;如果再调用一次push,则进入子NDC;
Pop可以让当前线程从上一级NDC中退出,但是一次只能退出一级。
Clear可以让当前线程从所有嵌套的NDC中退出。
Get可以得到当前NDC的名字,如果有嵌套,则不同级别之间的名字用空格隔开。
一个简单的例子NDCExam如下:
8、NDC
NDC是nested Diagnostic Context的缩写,意思是“嵌套的诊断上下文”。NDC是一种用来区分不同源代码中交替出现的日志的手段。当一个服务端程序同时记录好几个并行客户时,输出的日志会混杂在一起难以区分。但如果不同上下文的日志入口拥有一个特定的标识,则可以解决这个问题。NDC就是在这种情况下发挥作用。注意NDC是以线程为基础的,每个线程拥有一个NDC,每个NDC的操作仅对执行该操作的线程有效。
NDC的几个有用的方法是:push、pop、get和clear。注意它们都是静态函数:
Push可以让当前线程进入一个NDC,如果该NDC不存在,则根据push的参数创建一个NDC并进入;如果再调用一次push,则进入子NDC;
Pop可以让当前线程从上一级NDC中退出,但是一次只能退出一级。
Clear可以让当前线程从所有嵌套的NDC中退出。
Get可以得到当前NDC的名字,如果有嵌套,则不同级别之间的名字用空格隔开。
一个简单的例子NDCExam如下:
1 #include <iostream> 2 #include "log4cpp/NDC.hh" 3 #pragma comment(lib,"log4cppD.lib") 4 5 using namespace log4cpp; 6 int main(int argc, char** argv) 7 { 8 std::cout<<"1. empty "<<NDC::get()<<std::endl; 9 NDC::push("context1"); //添加第一层 10 std::cout<<"2. push context1: "<<NDC::get()<<std::endl; NDC::push("context2");//添加第二层 11 std::cout<<"3. push context2: "<<NDC::get()<<std::endl; NDC::push("context3");//添加第三层 12 std::cout<<"4. push context3: "<<NDC::get()<<std::endl; 13 std::cout<<"5. get depth: "<<NDC::getDepth()<<std::endl; //显示层数,有3层 14 std::cout<<"6. pop: "<<NDC::pop()<<std::endl; //弹出最上面的一层,这里是context3 15 std::cout<<"7. after pop: "<<NDC::get()<<std::endl;//弹出后,显示剩下的层 16 NDC::clear(); //将所有的层都清除 17 std::cout<<"8. clear: "<<NDC::get()<<std::endl; 18 return 0; 19 }
运行结果:
该例子来自log4cpp的例子程序,我做了简单的修改。在记录日志的时候,可以从NDC中得知当前线程的嵌套关系。
9、Log4cpp的自动内存管理
9.1 项目的多线程设置
VC中必须将项目设置为Debug MultiThreaded DLL,总之这个设置必须与你使用的Log4cpp库一致。如果你使用的是Release版本的log4cpp.dll,则应该设置为MultiThreaded DLL。
否则在程序结束时会报错,报错处的调用堆栈为:
log4cpp::BasicLayout::`vector deleting destructor"(unsigned int 1) + 122 bytes
log4cpp::LayoutAppender::~LayoutAppender() line 21 + 35 bytes
log4cpp::OstreamAppender::~OstreamAppender() line 28 + 15 bytes
log4cpp::OstreamAppender::`vector deleting destructor"(unsigned int 1) + 103 bytes
log4cpp::Category::removeAllAppenders() line 159 + 39 bytes
log4cpp::HierarchyMaintainer::shutdown() line 101 + 27 bytes
log4cpp::HierarchyMaintainer::~HierarchyMaintainer() line 36
log4cpp::LayoutAppender::~LayoutAppender() line 21 + 35 bytes
log4cpp::OstreamAppender::~OstreamAppender() line 28 + 15 bytes
log4cpp::OstreamAppender::`vector deleting destructor"(unsigned int 1) + 103 bytes
log4cpp::Category::removeAllAppenders() line 159 + 39 bytes
log4cpp::HierarchyMaintainer::shutdown() line 101 + 27 bytes
log4cpp::HierarchyMaintainer::~HierarchyMaintainer() line 36
9.2 Log4cpp的内存对象管理
也许读者已经注意到,在前面的所有代码中,log4cpp中所有动态分配的对象都没有手动释放。
Log4cpp中new出来的Category、Appender和Layout都不需要手动释放,因为Log4cpp使用了一个内部类来管理这些对象。此类的名称是HierarchyMaintainer,它负责管理Category的继承关系,在程序结束时,HierarchyMaintainer会依次释放所有Category,而Category则会依次释放拥有的有效Appender,Appender则会释放所有附属的Layout。如果程序员手动释放这些对象,则会造成内存报错。
从下面的代码可以看出这个特征:
也许读者已经注意到,在前面的所有代码中,log4cpp中所有动态分配的对象都没有手动释放。
Log4cpp中new出来的Category、Appender和Layout都不需要手动释放,因为Log4cpp使用了一个内部类来管理这些对象。此类的名称是HierarchyMaintainer,它负责管理Category的继承关系,在程序结束时,HierarchyMaintainer会依次释放所有Category,而Category则会依次释放拥有的有效Appender,Appender则会释放所有附属的Layout。如果程序员手动释放这些对象,则会造成内存报错。
从下面的代码可以看出这个特征:
appender->setLayout(new log4cpp::BasicLayout());
这个new出来的BasicLayout根本就没有保存其指针,所以它只能被log4cpp的内存管理类HierarchyMaintainer释放。
了解到HierarchyMaintainer的内存管理方法后,程序员在使用log4cpp时应该遵循以下几个使用原则:
1. 不要手动释放Category、Appender和Layout;
2. 同一个Appender不要加入多个Category,否则它会被释放多次从而导致程序崩溃;
3. 同一个Layout不要附着到多个Appender上,否则也会被释放多次导致程序崩溃;
下面这个简单的程序PointerErrorExam会造成经典的崩溃:
了解到HierarchyMaintainer的内存管理方法后,程序员在使用log4cpp时应该遵循以下几个使用原则:
1. 不要手动释放Category、Appender和Layout;
2. 同一个Appender不要加入多个Category,否则它会被释放多次从而导致程序崩溃;
3. 同一个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 = new log4cpp::OstreamAppender("osAppender", &cout);
osAppender->setLayout(new log4cpp::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;
}
#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 = new log4cpp::OstreamAppender("osAppender", &cout);
osAppender->setLayout(new log4cpp::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。
9.3 log4cpp::Category::shutdown()
在不使用log4cpp时可调用log4cpp::Category::shutdown(),其功能如同HierarchyMaintainer的内存清理。但如果不手动调用,在程序结束时HierarchyMaintainer会调用Category的析构函数来释放所有Appender。
10、利用配置文件定制日志
如同log4j一样,log4cpp也可以读取配置文件来定制Category、Appender和Layout对象。其配置文件格式基本类似于log4j,一个简单的配置文件log4cpp.conf例子如下(来自log4cpp的API手册):
9.3 log4cpp::Category::shutdown()
在不使用log4cpp时可调用log4cpp::Category::shutdown(),其功能如同HierarchyMaintainer的内存清理。但如果不手动调用,在程序结束时HierarchyMaintainer会调用Category的析构函数来释放所有Appender。
10、利用配置文件定制日志
如同log4j一样,log4cpp也可以读取配置文件来定制Category、Appender和Layout对象。其配置文件格式基本类似于log4j,一个简单的配置文件log4cpp.conf例子如下(来自log4cpp的API手册):
# a simple test config
log4j.rootCategory=DEBUG, rootAppender
log4j.category.sub1=A1
log4j.category.sub2=INFO
log4j.category.sub1.sub2=ERROR, A2
log4j.appender.rootAppender=org.apache.log4j.ConsoleAppender
log4j.appender.rootAppender.layout=org.apache.log4j.BasicLayout
log4j.appender.A1=org.apache.log4j.FileAppender
log4j.appender.A1.fileName=A1.log
log4j.appender.A1.layout=org.apache.log4j.BasicLayout
log4j.appender.A2=org.apache.log4j.ConsoleAppender
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=The message %m at time %d%n
log4j.rootCategory=DEBUG, rootAppender
log4j.category.sub1=A1
log4j.category.sub2=INFO
log4j.category.sub1.sub2=ERROR, A2
log4j.appender.rootAppender=org.apache.log4j.ConsoleAppender
log4j.appender.rootAppender.layout=org.apache.log4j.BasicLayout
log4j.appender.A1=org.apache.log4j.FileAppender
log4j.appender.A1.fileName=A1.log
log4j.appender.A1.layout=org.apache.log4j.BasicLayout
log4j.appender.A2=org.apache.log4j.ConsoleAppender
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=The message %m at time %d%n
这是一个标准的java属性文件。读取配置文件要依赖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 some info");
sub1.alert("A warning");
// sub3 only have A2 appender.
sub3.debug("This debug message will fail to write");
sub3.alert("All hands abandon ship");
sub3.critStream() "This will show up " 1 " critical message"
log4cpp::CategoryStream::ENDLINE;
sub3 log4cpp::Priority::ERROR
"And this will be an error"
log4cpp::CategoryStream::ENDLINE;
sub3.log(log4cpp::Priority::WARN, "This will be a logged warning");
return 0;
}
#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 some info");
sub1.alert("A warning");
// sub3 only have A2 appender.
sub3.debug("This debug message will fail to write");
sub3.alert("All hands abandon ship");
sub3.critStream() "This will show up " 1 " critical message"
log4cpp::CategoryStream::ENDLINE;
sub3 log4cpp::Priority::ERROR
"And this will be an error"
log4cpp::CategoryStream::ENDLINE;
sub3.log(log4cpp::Priority::WARN, "This will be a logged warning");
return 0;
}
该程序首先读入了配置文件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-29 21:54:09,515
1248875649 ALERT sub1.sub2 : All hands abandon ship
The message This will show up 1 critical message at time 2009-07-29 21:54:
09,531
1248875649 CRIT sub1.sub2 : This will show up The message And this will be an error at time 2009-07-29 21:54:09,531
1248875649 ERROR sub1.sub2 : And this will be an error
1248875649 ALERT sub1 : A warning
The message All hands abandon ship at time 2009-07-29 21:54:09,515
1248875649 ALERT sub1.sub2 : All hands abandon ship
The message This will show up 1 critical message at time 2009-07-29 21:54:
09,531
1248875649 CRIT sub1.sub2 : This will show up The message And this will be an error at time 2009-07-29 21:54:09,531
1248875649 ERROR sub1.sub2 : And this will be an error
11、DLL的版本问题
若在VC6中使用Log4cpp的DLL,则必须使用VC6编译链接生成的DLL,不能使用MSVS2008中生成的DLL,反之也是一样。否则会在运行时报错。
为此专门提供了两个版本的DLL,供大家使用。
12、小结
Log4cpp是一个小巧的c++库,易于上手,使用方便,不依赖其他库,具有跨平台性,并可与log4j、log4c、log4p等语言族共享其概念与使用方法。实在是进行日志记录、程序调试的利器。
作者:王学斌 + 孜孜拳拳(孜孜拳拳只是调试了源码,附加了一些注释和修改)
若在VC6中使用Log4cpp的DLL,则必须使用VC6编译链接生成的DLL,不能使用MSVS2008中生成的DLL,反之也是一样。否则会在运行时报错。
为此专门提供了两个版本的DLL,供大家使用。
12、小结
Log4cpp是一个小巧的c++库,易于上手,使用方便,不依赖其他库,具有跨平台性,并可与log4j、log4c、log4p等语言族共享其概念与使用方法。实在是进行日志记录、程序调试的利器。
作者:王学斌 + 孜孜拳拳(孜孜拳拳只是调试了源码,附加了一些注释和修改)