Log4cpp介紹及使用

Log4cpp是一個開源的C++類庫,它提供了在C++程序中使用日誌和跟蹤調試的功能。使用log4cpp,能夠很便利地將日誌或者跟蹤調試信息寫入字符流、內存字符串隊列、文件、回滾文件、調試器、Windows日誌、本地syslog和遠程syslog服務器中。html

一、Log4cpp簡介

  Log4cpp是個基於LGPL的開源項目,移植自Java的日誌處理跟蹤項目log4j,並保持了API上的一致。其相似的支持庫還包括Java(log4j),C++(log4cpp、log4cplus),C(log4c),python(log4p)等。python

  Log4cpp有以下優勢:linux

•提供了可擴展的多種日誌記錄方式;ios

•提供了NDC(嵌套診斷上下文),可用於多線程、多場景的跟蹤調試;c++

•提供了完整的日誌動態優先級控制,可隨時調整須要記錄的日誌優先級;程序員

•可經過配置文件完成全部配置並動態加載;編程

•性能優秀,內存佔用小,通過編譯後的log4cpp.dll大小僅有160kb;windows

•代碼級的平臺無關性,Log4cpp源代碼通過編譯後,適用於大多數主流的操做系統和開發工具;服務器

•概念清晰,學習和使用方便,熟練程序員一天以內便可很好地應用log4cpp進行開發。多線程

二、資源及使用

  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,將其中的工程都刪除,只保留log4cpp和log4cppDLL兩個工程。分別編譯它們的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例子程序

  本文包含了大量的例子程序,這些程序被組織爲多個工程,並放入了一個名爲WxbLogDsw的VC工做區。全部代碼被打包爲一個名爲WxbLogDsw.rar的壓縮文件,解壓後可在VC6以上版本中打開此工程並進行編譯運行。

 

三、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章節。

四、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,使用格式化字符串來描述輸出格式,其具體含義以下:

u %c category;

u %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,表示毫秒,佔三個十進制位。

u %m 消息;

u %n 換行符,會根據平臺的不一樣而不一樣,但對於用戶透明;

u %p 優先級;

u %r 自從layout被建立後的毫秒數;

u %R 從1970年1月1日0時開始到目前爲止的秒數;

u %u 進程開始到目前爲止的時鐘週期數;

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             // 輸出到遠程syslog服務器

Ø 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的使用很是簡單,在前面的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僅須要一個參數「名稱」,其使用很是簡單,下面是例子代碼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中得知當前線程的嵌套關係。

五、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。

六、利用配置文件定製日誌

如同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

七、DLL的版本問題

  若在VC6中使用Log4cpp的DLL,則必須使用VC6編譯連接生成的DLL,不能使用MSVS2008中生成的DLL,反之也是同樣。不然會在運行時報錯。

問題:因爲log4cpp-0.3.5rc3僅提供了vc6的工程文件,所以,使用vs2005打開後,須要進行轉換。可是轉換後,不能正確編譯,提示Custom Build Step時出現了錯誤。

分 析:由於log4cpp在生成NTEventLogAppender.dll時,須要鏈接NTEventLogCategories.mc文件。因此,項目設置了自定義的生成步驟去生成NTEventLogAppender.dll。但從vc6的工程文件轉換時,這些步驟卻沒有正確的轉換過來。從而出現上述問題。

解決方法:從新填寫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文件添加到項目中從新編譯

八、小結

  Log4cpp是一個小巧的c++庫,易於上手,使用方便,不依賴其餘庫,具備跨平臺性,並可與log4j、log4c、log4p等語言族共享其概念與使用方法。實在是進行日誌記錄、程序調試的利器。

相關文章
相關標籤/搜索