日誌模組(一標頭檔案就實現了日誌記錄)

pamxy發表於2013-07-16

轉自:http://blog.csdn.net/wenhm/article/details/4445955

溫輝敏(wenhm@sina.com)  2009-8-2 

摘要:本文介紹了一個C/C++日誌記錄模組,它是對開源log4c進行的封裝和修正的基礎上[1][[2][3][4],將所有的程式碼都整合到一個.h標頭檔案中。帶來的革命性突破是日誌模組的使用是那麼的方便和簡單:只要使用一個.h標頭檔案即可無縫的將日誌模組嵌入到你的工程中去,不需要再新增額外的的.c/.cpp或是連結lib/dll等,整個日誌模組就是一個標頭檔案。日誌輸出格式方面,提供了C語言傳統的字元printf格式輸出,同時也為C++提供了ostream方式的流式輸出。

關鍵字: 日誌 回滾 日誌記錄 log4c

日誌模組原始碼和Demohttp://download.csdn.net/source/1568873,或是csdn上搜尋上述關鍵字即可找到。

作者部落格:http://blog.csdn.net/wenhm/

 

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_TRACEstrBuffersizeof(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_TRACEstrBuffersizeof(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的日誌中沒有輸出檔名和行號,加上了檔名和行號。


相關文章