log4cplus是C++編寫的開源的日誌系統,前身是java編寫的log4j系統.受Apache Software License 保護。作者是Tad E. Smith。log4cplus具有線程安全、靈活、以及多粒度控制的特點,通過將信息劃分 優先級使其可以面向程序調試、運行、測試、和維護等全生命週期; 你可以選擇將信息輸出到屏幕、文件、 NT event log、甚至是遠程服務器;通過指定策略對日誌進行定期備份等等。
### 使用前的配置 ###
在工程中加入LOG4CPLUS_STATIC。
### 構成要素介紹 ###
Layouts :佈局器,控制輸出消息的格式. Appenders :掛接器,與佈局器緊密配合,將特定格式的消息輸出到所掛接的設備終端 (如屏幕,文件等等)。 Logger :記錄器,保存並跟蹤對象日誌信息變更的實體,當你需要對一個對象進行 記錄時,就需要生成一個logger。 Categories :分類器,層次化(hierarchy)的結構,用於對被記錄信息的分類,層次中 每一個節點維護一個logger的所有信息。 Priorities :優先權,包括TRACE, DEBUG, INFO, WARNING, ERROR, FATAL。
### 基本使用 ###使用log4cplus有六個基本步驟:1. 實例化一個appender對象 2. 實例化一個layout對象 3. 將layout對象綁定(attach)到appender對象 4. 實例化一個logger對象,調用靜態函數:log4cplus::Logger::getInstance("logger_name") 5. 將appender對象綁定(attach)到logger對象,如省略此步驟,標準輸出(屏幕)appender對象會綁定到logger 6. 設置logger的優先級,如省略此步驟,各種有限級的消息都將被記錄下面通過一些例子來了解log4cplus的基本使用。〖例1〗/**//* 嚴格實現步驟1-6,appender輸出到屏幕, 其中的佈局格式和LogLevel後面會詳細解釋。*/#include <log4cplus/logger.h>#include <log4cplus/consoleappender.h>#include <log4cplus/layout.h> using namespace log4cplus;using namespace log4cplus::helpers; int main(){ /**//* step 1: Instantiate an appender object */ SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test"); /**//* step 2: Instantiate a layout object */ std::string pattern = "%d{%m/%d/%y %H:%M:%S} - %m [%l]%n"; std::auto_ptr _layout(new PatternLayout(pattern)); /**//* step 3: Attach the layout object to the appender */ _append->setLayout( _layout ); /**//* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test"); /**//* step 5: Attach the appender object to the logger */ _logger.addAppender(_append); /**//* step 6: Set a priority for the logger */ _logger.setLogLevel(ALL_LOG_LEVEL); /**//* log activity */ LOG4CPLUS_DEBUG(_logger, "This is the FIRST log message") sleep(1); LOG4CPLUS_WARN(_logger, "This is the SECOND log message") return 0;}輸出結果: 10/14/04 09:06:24 - This is the FIRST log message... [main.cpp:31] 10/14/04 09:06:25 - This is the SECOND log message... [main.cpp:33]〖例2〗/**//* 簡潔使用模式,appender輸出到屏幕。*/#include <log4cplus/logger.h>#include <log4cplus/consoleappender.h> using namespace log4cplus;using namespace log4cplus::helpers; int main(){ /**//* step 1: Instantiate an appender object */ SharedAppenderPtr _append(new ConsoleAppender()); _append->setName("append test"); /**//* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test"); /**//* step 5: Attach the appender object to the logger */ _logger.addAppender(_append); /**//* log activity */ LOG4CPLUS_DEBUG(_logger, "This is the FIRST log message") sleep(1); LOG4CPLUS_WARN(_logger, "This is the SECOND log message") return 0;}輸出結果: DEBUG - This is the FIRST log message... WARN - This is the SECOND log message...〖例3〗/**//* iostream模式,appender輸出到屏幕。*/#include <log4cplus/logger.h>#include <log4cplus/consoleappender.h>#include <iomanip> using namespace log4cplus; int main(){ /**//* step 1: Instantiate an appender object */ SharedAppenderPtr _append(new ConsoleAppender()); _append->setName("append test"); /**//* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test"); /**//* step 5: Attach the appender object to the logger */ _logger.addAppender(_append); /**//* log activity */ LOG4CPLUS_TRACE(_logger, "This is" << " just a t" << "est." << std::endl) LOG4CPLUS_DEBUG(_logger, "This is a bool: " << true) LOG4CPLUS_INFO(_logger, "This is a char: " << 'x') LOG4CPLUS_WARN(_logger, "This is a int: " << 1000) LOG4CPLUS_ERROR(_logger, "This is a long(hex): " << std::hex << 100000000) LOG4CPLUS_FATAL(_logger, "This is a double: " << std::setprecision(15) << 1.2345234234) return 0;}輸出結果: DEBUG - This is a bool: 1 INFO - This is a char: x WARN - This is a int: 1000 ERROR - This is a long(hex): 5f5e100 FATAL - This is a double: 1.2345234234〖例4〗/**//* 調試模式,通過loglog來控制輸出調試、警告或錯誤信息,appender輸出到屏幕。*/#include <iostream>#include <log4cplus/helpers/loglog.h> using namespace log4cplus::helpers; void printMsgs(void){ std::cout << "Entering printMsgs()" << std::endl; LogLog::getLogLog()->debug("This is a Debug statement"); LogLog::getLogLog()->warn("This is a Warning"); LogLog::getLogLog()->error("This is a Error"); std::cout << "Exiting printMsgs()" << std::endl << std::endl;} int main(){ /**//* LogLog類實現了debug, warn, error 函數用於輸出調試、警告或錯誤信息, 同時提供了兩個方法來進一步控制所輸出的信息,其中: setInternalDebugging方法用來控制是否屏蔽輸出信息中的調試信息,當輸入 參數爲false則屏蔽,缺省設置爲false。 setQuietMode方法用來控制是否屏蔽所有輸出信息,當輸入參數爲true則屏蔽, 缺省設置爲false。 LogLog::getLogLog()->setInternalDebugging(false); */ printMsgs(); std::cout << "Turning on debug" << std::endl; LogLog::getLogLog()->setInternalDebugging(true); printMsgs(); std::cout << "Turning on quiet mode" << std::endl; LogLog::getLogLog()->setQuietMode(true); printMsgs(); return 0;}輸出結果: Entering printMsgs()... log4cplus:WARN This is a Warning... log4cplus:ERROR This is a Error... Exiting printMsgs()...Turning on debug... Entering printMsgs()... log4cplus: This is a Debug statement... log4cplus:WARN This is a Warning... log4cplus:ERROR This is a Error... Exiting printMsgs()...Turning on quiet mode... Entering printMsgs()... Exiting printMsgs()...需要指出的是,輸出信息中總是包含"log4cplus:"前綴,有時候會感覺不爽,這是因爲LogLog在實現時候死定了要這麼寫:LogLog::LogLog() : mutex(LOG4CPLUS_MUTEX_CREATE), debugEnabled(false), quietMode(false), PREFIX( LOG4CPLUS_TEXT("log4cplus: ") ), WARN_PREFIX( LOG4CPLUS_TEXT("log4cplus:WARN ") ), ERR_PREFIX( LOG4CPLUS_TEXT("log4cplus:ERROR ") ) { }改動即可.〖例5〗/* 文件模式,appender輸出到文件。*/ #include <log4cplus/logger.h> #include <log4cplus/fileappender.h> using namespace log4cplus; int main() { /* step 1: Instantiate an appender object */ SharedAppenderPtr _append(new FileAppender("Test.log")); _append->setName("file log test"); /* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test.subtestof_filelog"); /* step 5: Attach the appender object to the logger */ _logger.addAppender(_append); /* log activity */ int i; for( i = 0; i < 5; ++i ) { LOG4CPLUS_DEBUG(_logger, "Entering loop #" << i << "End line #") } return 0; }輸出結果(Test.log文件):DEBUG - Entering loop #0End line # DEBUG - Entering loop #1End line # DEBUG - Entering loop #2End line # DEBUG - Entering loop #3End line # DEBUG - Entering loop #4End line #### 如何控制輸出消息的格式 ###前面已經講過,log4cplus通過佈局器(Layouts)來控制輸出的格式,log4cplus提供了三種類型的Layouts, 分別是SimpleLayout、PatternLayout、和TTCCLayout。其中:1. SimpleLayout 是一種簡單格式的佈局器,在輸出的原始信息之前加上LogLevel和一個"-"。比如以下代碼片段:/**//* step 1: Instantiate an appender object */ SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test"); /**//* step 2: Instantiate a layout object */ std::auto_ptr _layout(new log4cplus::SimpleLayout()); /**//* step 3: Attach the layout object to the appender */ _append->setLayout( _layout ); /**//* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test"); /**//* step 5: Attach the appender object to the logger */ _logger.addAppender(_append); /**//* log activity */ LOG4CPLUS_DEBUG(_logger, "This is the simple formatted log message")將打印結果: DEBUG - This is the simple formatted log message...2. PatternLayout 是一種有詞法分析功能的模式佈局器,一提起模式就會想起正則表達式,這裏的模式和正則表達式類似,但是 遠比後者簡單,能夠對預定義的標識符(稱爲conversion specifiers)進行解析,轉換成特定格式輸出。以下 代碼片段演示瞭如何使用PatternLayout:
/**//* step 1: Instantiate an appender object */ SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test"); /**//* step 2: Instantiate a layout object */ std::string pattern = "%d{%m/%d/%y %H:%M:%S} - %m [%l]%n"; std::auto_ptr _layout(new PatternLayout(pattern)); /**//* step 3: Attach the layout object to the appender */ _append->setLayout( _layout );
/**//* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test_logger.subtest");
/**//* step 5: Attach the appender object to the logger */ _logger.addAppender(_append);
/**//* log activity */ LOG4CPLUS_DEBUG(_logger, "teststr")輸出結果: 10/16/04 18:51:25 - teststr [main.cpp:51]可以看出通過填寫特定格式的模式字符串"pattern",原始信息被包含到一堆有格式的信息當中了,這就使得 用戶可以根據自身需要來定製顯示內容。"pattern"可以包含普通字符串和預定義的標識符,其中:(1)普通字符串,能夠被直接顯示的信息。 (2)預定義標識符,通過"%"與一個或多個字符共同構成預定義的標識符,能夠產生出特定格式信息。關於預定義標識符,log4cplus文檔中提供了詳細的格式說明,我每種都試了一下,以上述代碼爲例,根據不同 的pattern,各種消息格式使用情況列舉如下:(1)"%%",轉義爲%, 即,std::string pattern = "%%" 時輸出: "%" (2)"%c",輸出logger名稱,比如std::string pattern ="%c" 時輸出: "test_logger.subtest", 也可以控制logger名稱的顯示層次,比如"%c{1}"時輸出"test_logger",其中數字表示層次。 (3)"%D",顯示本地時間,當std::string pattern ="%D" 時輸出:"2004-10-16 18:55:45",%d顯示標準時間, 所以當std::string pattern ="%d" 時輸出 "2004-10-16 10:55:45" (因爲我們是東8區,差8個小時啊)。 可以通過%d{...}定義更詳細的顯示格式,比如%d{%H:%M:%s}表示要顯示小時:分鐘:秒。大括號中可顯示的 預定義標識符如下: %a -- 表示禮拜幾,英文縮寫形式,比如"Fri" %A -- 表示禮拜幾,比如"Friday" %b -- 表示幾月份,英文縮寫形式,比如"Oct" %B -- 表示幾月份,"October" %c -- 標準的日期+時間格式,如 "Sat Oct 16 18:56:19 2004" %d -- 表示今天是這個月的幾號(1-31)"16" %H -- 表示當前時刻是幾時(0-23),如 "18" %I -- 表示當前時刻是幾時(1-12),如 "6" %j -- 表示今天是哪一天(1-366),如 "290" %m -- 表示本月是哪一月(1-12),如 "10" %M -- 表示當前時刻是哪一分鐘(0-59),如 "59" %p -- 表示現在是上午還是下午, AM or PM %q -- 表示當前時刻中毫秒部分(0-999),如 "237" %Q -- 表示當前時刻中帶小數的毫秒部分(0-999.999),如 "430.732" %S -- 表示當前時刻的多少秒(0-59),如 "32" %U -- 表示本週是今年的第幾個禮拜,以週日爲第一天開始計算(0-53),如 "41" %w -- 表示禮拜幾,(0-6, 禮拜天爲0),如 "6" %W -- 表示本週是今年的第幾個禮拜,以週一爲第一天開始計算(0-53),如 "41" %x -- 標準的日期格式,如 "10/16/04" %X -- 標準的時間格式,如 "19:02:34" %y -- 兩位數的年份(0-99),如 "04" %Y -- 四位數的年份,如 "2004" %Z -- 時區名,比如 "GMT"(4)"%F",輸出當前記錄器所在的文件名稱,比如std::string pattern ="%F" 時輸出: "main.cpp" (5)"%L",輸出當前記錄器所在的文件行號,比如std::string pattern ="%L" 時輸出: "51" (6)"%l",輸出當前記錄器所在的文件名稱和行號,比如std::string pattern ="%L" 時輸出: "main.cpp:51" (7)"%m",輸出原始信息,比如std::string pattern ="%m" 時輸出: "teststr",即上述代碼中 LOG4CPLUS_DEBUG的第二個參數,這種實現機制可以確保原始信息被嵌入到帶格式的信息中。 (8)"%n",換行符,沒什麼好解釋的 (9)"%p",輸出LogLevel,比如std::string pattern ="%p" 時輸出: "DEBUG" (10)"%t",輸出記錄器所在的線程ID,比如std::string pattern ="%t" 時輸出: "1075298944" (11)"%x",嵌套診斷上下文NDC (nested diagnostic context) 輸出,從堆棧中彈出上下文信息,NDC可以用對 不同源的log信息(同時地)交叉輸出進行區分,關於NDC方面的詳細介紹會在下文中提到。 (12)格式對齊,比如std::string pattern ="%-10m"時表示左對齊,寬度是10,此時會輸出"teststr ",當 然其它的控制字符也可以相同的方式來使用,比如"%-12d","%-5p"等等(剛接觸log4cplus文檔時還以爲 "%-5p"整個字符串代表LogLevel呢,呵呵)。3. TTCCLayout 是在PatternLayout基礎上發展的一種缺省的帶格式輸出的佈局器, 其格式由時間,線程ID,Logger和NDC 組 成(consists of time, thread, Logger and nested diagnostic context information, hence the name), 因而得名(怎麼得名的?Logger裏哪裏有那個"C"的縮寫啊!名字起得真夠爛的,想扁人)。提供給那些想顯示 典型的信息(一般情況下夠用了)又懶得配置pattern的同志們。TTCCLayout在構造時有機會選擇顯示本地時間或GMT時間,缺省是按照本地時間顯示: TTCCLayout::TTCCLayout(bool use_gmtime = false)以下代碼片段演示瞭如何使用TTCCLayout:/**//* step 1: Instantiate an appender object */ SharedObjectPtr _append (new ConsoleAppender()); _append->setName("append for test"); /**//* step 2: Instantiate a layout object */ std::auto_ptr _layout(new TTCCLayout()); /**//* step 3: Attach the layout object to the appender */ _append->setLayout( _layout ); /**//* step 4: Instantiate a logger object */ Logger _logger = Logger::getInstance("test_logger"); /**//* step 5: Attach the appender object to the logger */ _logger.addAppender(_append); /**//* log activity */ LOG4CPLUS_DEBUG(_logger, "teststr")輸出結果: 10-16-04 19:08:27,501 [1075298944] DEBUG test_logger <> - teststr當構造TTCCLayout對象時選擇GMT時間格式時:/**//* step 2: Instantiate a layout object */ std::auto_ptr _layout(new TTCCLayout(true));輸出結果: 10-16-04 11:12:47,678 [1075298944] DEBUG test_logger <> - teststr本文介紹了控制log信息格式的相關知識,下一部分將詳細介紹log信息的幾種文件操作方式。將log信息記錄到文件應該說是日誌系統的一個基本功能,log4cplus在此基礎上,提供了更多的功能,可以按照你預先設定的大小來決定是否轉儲,當超過該大小,後續log信息會另存到新文件中,依次類推;或者按照日期來決定是否轉儲。本文將詳細介紹這些用法。
### 如何將log記錄到文件 ###我們在例5中給出了一個將log記錄到文件的例子,用的是FileAppender類實現的,log4cplus提供了三個類用於 文件操作,它們是FileAppender類、RollingFileAppender類、DailyRollingFileAppender類。1. FileAppender類實現了基本的文件操作功能,構造函數如下:FileAppender(const log4cplus::tstring& filename, LOG4CPLUS_OPEN_MODE_TYPE mode = LOG4CPLUS_FSTREAM_NAMESPACE::ios::trunc, bool immediateFlush = true); filename : 文件名 mode : 文件類型,可選擇的文件類型包括app、ate、binary、in、out、trunc,因爲實際上只是對 stl的一個簡單包裝,呵呵,這裏就不多講了。缺省是trunc,表示將先前文件刪除。 immediateFlush :緩衝刷新標誌,如果爲true表示每向文件寫一條記錄就刷新一次緩存,否則直到FileAppender 被關閉或文件緩存已滿才更新文件,一般是要設置true的,比如你往文件寫的過程中出現 了錯誤(如程序非正常退出),即使文件沒有正常關閉也可以保證程序終止時刻之前的所有 記錄都會被正常保存。FileAppender類的使用情況請參考例5,這裏不再贅述。2. RollingFileAppender類構造函數如下: log4cplus::RollingFileAppender::RollingFileAppender(const log4cplus::tstring& filename, long maxFileSize, int maxBackupIndex, bool immediateFlush)filename : 文件名 maxFileSize : 文件的最大尺寸 maxBackupIndex : 最大記錄文件數 immediateFlush : 緩衝刷新標誌 RollingFileAppender類可以根據你預先設定的大小來決定是否轉儲,當超過該大小,後續log信息會另存到新 文件中,除了定義每個記錄文件的大小之外,你還要確定在RollingFileAppender類對象構造時最多需要多少個 這樣的記錄文件(maxBackupIndex+1),當存儲的文件數目超過maxBackupIndex+1時,會刪除最早生成的文件, 保證整個文件數目等於maxBackupIndex+1。然後繼續記錄,比如以下代碼片段:#define LOOP_COUNT 200000 SharedAppenderPtr _append(new RollingFileAppender("Test.log", 5*1024, 5)); _append->setName("file test"); _append->setLayout( std::auto_ptr(new TTCCLayout()) ); Logger::getRoot().addAppender(_append); Logger root = Logger::getRoot(); Logger test = Logger::getInstance("test"); Logger subTest = Logger::getInstance("test.subtest"); for(int i=0; i { NDCContextCreator _context("loop"); LOG4CPLUS_DEBUG(subTest, "Entering loop #" << i) }運行結果:運行後會產生6個輸出文件,Test.log、Test.log.1、Test.log.2、Test.log.3、Test.log.4、Test.log.5 其中Test.log存放着最新寫入的信息,而最後一個文件中並不包含第一個寫入信息,說明已經被不斷更新了。 需要指出的是,這裏除了Test.log之外,每個文件的大小都是200K,而不是我們想像中的5K,這是因爲 log4cplus中隱含定義了文件的最小尺寸是200K,只有大於200K的設置才生效,<= 200k的設置都會被認爲是 200K.3. DailyRollingFileAppender類構造函數如下: DailyRollingFileAppender::DailyRollingFileAppender(const log4cplus::tstring& filename, DailyRollingFileSchedule schedule, bool immediateFlush, int maxBackupIndex) filename : 文件名 schedule : 存儲頻度 immediateFlush : 緩衝刷新標誌 maxBackupIndex : 最大記錄文件數DailyRollingFileAppender類可以根據你預先設定的頻度來決定是否轉儲,當超過該頻度,後續log信息會另存 到新文件中,這裏的頻度包括:MONTHLY(每月)、WEEKLY(每週)、DAILY(每日)、TWICE_DAILY(每兩天)、 HOURLY(每時)、MINUTELY(每分)。maxBackupIndex的含義同上所述,比如以下代碼片段:SharedAppenderPtr _append(new DailyRollingFileAppender("Test.log", MINUTELY, true, 5)); _append->setName("file test"); _append->setLayout( std::auto_ptr(new TTCCLayout()) ); Logger::getRoot().addAppender(_append); Logger root = Logger::getRoot(); Logger test = Logger::getInstance("test"); Logger subTest = Logger::getInstance("test.subtest"); for(int i=0; i { NDCContextCreator _context("loop"); LOG4CPLUS_DEBUG(subTest, "Entering loop #" << i) }運行結果:運行後會以分鐘爲單位,分別生成名爲Test.log.2004-10-17-03-03、Test.log.2004-10-17-03-04和 Test.log.2004-10-17-03-05這樣的文件。需要指出的是,剛看到按照頻度(如HOURLY、MINUTELY)轉儲這樣的概念,以爲log4cplus提供了內部定時器, 感覺很奇怪,因爲日誌系統不應該主動記錄,而loging事件總是應該被動觸發的啊。仔細看了源代碼後才知道 這裏的"頻度"並不是你寫入文件的速度,其實是否轉儲的標準並不依賴你寫入文件的速度,而是依賴於寫入 的那一時刻是否滿足了頻度條件,即是否超過了以分鐘、小時、周、月爲單位的時間刻度,如果超過了就另存。本部分詳細介紹log信息的幾種文件操作方式,下面將重點介紹一下如何有選擇地控制log信息的輸出。日誌系統的另一個基本功能就是能夠讓使用者按照自己的意願來控制什麼時候,哪些log信息可以輸出。 如果能夠讓用戶在任意時刻設置允許輸出的LogLevel的信息就好了,log4cplus通過LogLevelManager、 LogLog、Filter三種方式實現了上述功能。### 優先級控制 ###在研究LogLevelManager之前,首先介紹一下log4cplus中logger的存儲機制,在log4cplus中,所有 logger都通過一個層次化的結構(其實內部是hash表)來組織的,有一個Root級別的logger,可以通 過以下方法獲取:Logger root = Logger::getRoot(); 用戶定義的logger都有一個名字與之對應,比如:Logger test = Logger::getInstance("test"); 可以定義該logger的子logger:Logger subTest = Logger::getInstance("test.subtest"); 注意Root級別的logger只有通過getRoot方法獲取,Logger::getInstance("root")獲得的是它的 子對象而已。有了這些具有父子關係的logger之後可分別設置其LogLevel,比如:root.setLogLevel( ... ); Test.setLogLevel( ... ); subTest.setLogLevel( ... );logger的這種父子關聯性會體現在優先級控制方面,log4cplus將輸出的log信息按照LogLevel (從低到高)分爲:NOT_SET_LOG_LEVEL ( -1) :接受缺省的LogLevel,如果有父logger則繼承它的LogLevel ALL_LOG_LEVEL ( 0) :開放所有log信息輸出 TRACE_LOG_LEVEL ( 0) :開放trace信息輸出(即ALL_LOG_LEVEL) DEBUG_LOG_LEVEL (10000) :開放debug信息輸出 INFO_LOG_LEVEL (20000) :開放info信息輸出 WARN_LOG_LEVEL (30000) :開放warning信息輸出 ERROR_LOG_LEVEL (40000) :開放error信息輸出 FATAL_LOG_LEVEL (50000) :開放fatal信息輸出 OFF_LOG_LEVEL (60000) :關閉所有log信息輸出LogLevelManager負責設置logger的優先級,各個logger可以通過setLogLevel設置自己的優先級, 當某個logger的LogLevel設置成NOT_SET_LOG_LEVEL時,該logger會繼承父logger的優先級,另外, 如果定義了重名的多個logger, 對其中任何一個的修改都會同時改變其它logger,我們舉例說明:〖例6〗#include "log4cplus/logger.h"#include "log4cplus/consoleappender.h"#include "log4cplus/loglevel.h"#include <iostream>
using namespace std;using namespace log4cplus;
int main(){ SharedAppenderPtr _append(new ConsoleAppender()); _append->setName("test"); Logger::getRoot().addAppender(_append); Logger root = Logger::getRoot();
Logger test = Logger::getInstance("test"); Logger subTest = Logger::getInstance("test.subtest"); LogLevelManager& llm = getLogLevelManager();
cout << endl << "Before Setting, Default LogLevel" << endl; LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))
cout << endl << "Setting test.subtest to WARN" << endl; subTest.setLogLevel(WARN_LOG_LEVEL); LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))
cout << endl << "Setting test.subtest to TRACE" << endl; test.setLogLevel(TRACE_LOG_LEVEL); LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))
cout << endl << "Setting test.subtest to NO_LEVEL" << endl; subTest.setLogLevel(NOT_SET_LOG_LEVEL); LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()) << '\n')
cout << "create a logger test_bak, named \"test_\", too. " << endl; Logger test_bak = Logger::getInstance("test"); cout << "Setting test to INFO, so test_bak also be set to INFO" << endl; test.setLogLevel(INFO_LOG_LEVEL); LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel())) LOG4CPLUS_FATAL(root, "test_bak: " << llm.toString(test_bak.getChainedLogLevel()))
return 0;}輸出結果:Before Setting, Default LogLevel FATAL - root: DEBUG FATAL - test: DEBUG FATAL - test.subtest: DEBUGSetting test.subtest to WARN FATAL - root: DEBUG FATAL - test: DEBUG FATAL - test.subtest: WARNSetting test.subtest to TRACE FATAL - root: DEBUG FATAL - test: TRACE FATAL - test.subtest: WARNSetting test.subtest to NO_LEVEL FATAL - root: DEBUG FATAL - test: TRACE FATAL - test.subtest: TRACEcreate a logger test_bak, named "test_", too. Setting test to INFO, so test_bak also be set to INFO FATAL - test: INFO FATAL - test_bak: INFO下面的例子演示瞭如何通過設置LogLevel來控制用戶的log信息輸出:〖例7〗#include "log4cplus/logger.h"#include "log4cplus/consoleappender.h"#include "log4cplus/loglevel.h"#include <iostream>
using namespace std;using namespace log4cplus;
void ShowMsg(void){ LOG4CPLUS_TRACE(Logger::getRoot(),"info") LOG4CPLUS_DEBUG(Logger::getRoot(),"info") LOG4CPLUS_INFO(Logger::getRoot(),"info") LOG4CPLUS_WARN(Logger::getRoot(),"info") LOG4CPLUS_ERROR(Logger::getRoot(),"info") LOG4CPLUS_FATAL(Logger::getRoot(),"info")}
int main(){ SharedAppenderPtr _append(new ConsoleAppender()); _append->setName("test"); _append->setLayout(std::auto_ptr(new TTCCLayout())); Logger root = Logger::getRoot(); root.addAppender(_append);
cout << endl << "all-log allowed" << endl; root.setLogLevel(ALL_LOG_LEVEL); ShowMsg();
cout << endl << "trace-log and above allowed" << endl; root.setLogLevel(TRACE_LOG_LEVEL); ShowMsg();
cout << endl << "debug-log and above allowed" << endl; root.setLogLevel(DEBUG_LOG_LEVEL); ShowMsg();
cout << endl << "info-log and above allowed" << endl; root.setLogLevel(INFO_LOG_LEVEL); ShowMsg();
cout << endl << "warn-log and above allowed" << endl; root.setLogLevel(WARN_LOG_LEVEL); ShowMsg();
cout << endl << "error-log and above allowed" << endl; root.setLogLevel(ERROR_LOG_LEVEL); ShowMsg();
cout << endl << "fatal-log and above allowed" << endl; root.setLogLevel(FATAL_LOG_LEVEL); ShowMsg();
cout << endl << "log disabled" << endl; root.setLogLevel(OFF_LOG_LEVEL); ShowMsg();
return 0;}輸出結果:all-log allowed 10-17-04 10:11:40,587 [1075298944] TRACE root <> - info 10-17-04 10:11:40,590 [1075298944] DEBUG root <> - info 10-17-04 10:11:40,591 [1075298944] INFO root <> - info 10-17-04 10:11:40,591 [1075298944] WARN root <> - info 10-17-04 10:11:40,592 [1075298944] ERROR root <> - info 10-17-04 10:11:40,592 [1075298944] FATAL root <> - infotrace-log and above allowed 10-17-04 10:11:40,593 [1075298944] TRACE root <> - info 10-17-04 10:11:40,593 [1075298944] DEBUG root <> - info 10-17-04 10:11:40,594 [1075298944] INFO root <> - info 10-17-04 10:11:40,594 [1075298944] WARN root <> - info 10-17-04 10:11:40,594 [1075298944] ERROR root <> - info 10-17-04 10:11:40,594 [1075298944] FATAL root <> - infodebug-log and above allowed 10-17-04 10:11:40,595 [1075298944] DEBUG root <> - info 10-17-04 10:11:40,595 [1075298944] INFO root <> - info 10-17-04 10:11:40,596 [1075298944] WARN root <> - info 10-17-04 10:11:40,596 [1075298944] ERROR root <> - info 10-17-04 10:11:40,596 [1075298944] FATAL root <> - infoinfo-log and above allowed 10-17-04 10:11:40,597 [1075298944] INFO root <> - info 10-17-04 10:11:40,597 [1075298944] WARN root <> - info 10-17-04 10:11:40,597 [1075298944] ERROR root <> - info 10-17-04 10:11:40,598 [1075298944] FATAL root <> - infowarn-log and above allowed 10-17-04 10:11:40,598 [1075298944] WARN root <> - info 10-17-04 10:11:40,598 [1075298944] ERROR root <> - info 10-17-04 10:11:40,599 [1075298944] FATAL root <> - infoerror-log and above allowed 10-17-04 10:11:40,599 [1075298944] ERROR root <> - info 10-17-04 10:11:40,600 [1075298944] FATAL root <> - infofatal-log and above allowed 10-17-04 10:11:40,600 [1075298944] FATAL root <> - infolog disabled用戶也可以自行定義LogLevel,操作比較簡單,首先要定義LEVEL值,比如HELLO_LOG_LEVEL定義如下:/* DEBUG_LOG_LEVEL < HELLO_LOG_LEVEL < INFO_LOG_LEVEL */ const LogLevel HELLO_LOG_LEVEL = 15000;然後定義以下宏即可:/* define MACRO LOG4CPLUS_HELLO */ #define LOG4CPLUS_HELLO(logger, logEvent) \ if(logger.isEnabledFor(HELLO_LOG_LEVEL)) { \ log4cplus::tostringstream _log4cplus_buf; \ _log4cplus_buf << logEvent; \ logger.forcedLog(HELLO_LOG_LEVEL, _log4cplus_buf.str(), __FILE__, __LINE__); \ }不過log4cplus沒有提供給用戶一個接口來實現LEVEL值與字符串的轉換,所以當帶格式輸出LogLevel字符 串時候會顯示"UNKNOWN", 不夠理想。比如用TTCCLayout控制輸出的結果可能會如下所示:10-17-04 11:17:51,124 [1075298944] UNKNOWN root <> - info而不是期望的以下結果: 10-17-04 11:17:51,124 [1075298944] HELLO root <> - info要想實現第二種結果,按照log4cplus現有的接口機制,只能改其源代碼後重新編譯,方法是在loglevel.cxx 中加入:#define _HELLO_STRING LOG4CPLUS_TEXT("HELLO")然後修改log4cplus::tstring defaultLogLevelToStringMethod(LogLevel ll)函數,增加一個判斷:case HELLO_LOG_LEVEL: return _HELLO_STRING;重新編譯log4cplus源代碼後生成庫文件,再使用時即可實現滿意效果。### 調試模式 ###即通過loglog來控制輸出調試、警告或錯誤信息,見例4,這裏不再贅述。### 基於腳本配置來過濾log信息 ###除了通過程序實現對log環境的配置之外,log4cplus通過PropertyConfigurator類實現了基於腳本配置的功能。 通過腳本可以完成對logger、appender和layout的配置,因此可以解決怎樣輸出,輸出到哪裏的問題,我將在 全文的最後一部分中提到多線程環境中如何利用腳本配置來配合實現性能測試,本節將重點介紹基腳本實現過 濾log信息的功能。首先簡單介紹一下腳本的語法規則:包括Appender的配置語法和logger的配置語法,其中:1.Appender的配置語法:(1)設置名稱:/*設置方法*/ log4cplus.appender.appenderName=fully.qualified.name.of.appender.class例如(列舉了所有可能的Appender,其中SocketAppender後面會講到): log4cplus.appender.append_1=log4cplus::ConsoleAppender log4cplus.appender.append_2=log4cplus::FileAppender log4cplus.appender.append_3=log4cplus::RollingFileAppender log4cplus.appender.append_4=log4cplus::DailyRollingFileAppender log4cplus.appender.append_4=log4cplus::SocketAppender(2)設置Filter:包括選擇過濾器和設置過濾條件,可選擇的過濾器包括:LogLevelMatchFilter、LogLevelRangeFilter、 和StringMatchFilter:對LogLevelMatchFilter來說,過濾條件包括LogLevelToMatch和AcceptOnMatch(true|false), 只有 當log信息的LogLevel值與LogLevelToMatch相同,且AcceptOnMatch爲true時纔會匹配。LogLevelRangeFilter來說,過濾條件包括LogLevelMin、LogLevelMax和AcceptOnMatch,只有當log信息 的LogLevel在LogLevelMin、LogLevelMax之間同時AcceptOnMatch爲true時纔會匹配。對StringMatchFilter來說,過濾條件包括StringToMatch和AcceptOnMatch,只有當log信息的LogLevel值 與StringToMatch對應的LogLevel值與相同, 且AcceptOnMatch爲true時會匹配。過濾條件處理機制類似於IPTABLE的Responsibility chain,(即先deny、再allow)不過執行順序剛好相反, 後寫的條件會被先執行,比如:log4cplus.appender.append_1.filters.1=log4cplus::spi::LogLevelMatchFilter log4cplus.appender.append_1.filters.1.LogLevelToMatch=TRACE log4cplus.appender.append_1.filters.1.AcceptOnMatch=true #log4cplus.appender.append_1.filters.2=log4cplus::spi::DenyAllFilter會首先執行filters.2的過濾條件,關閉所有過濾器,然後執行filters.1,僅匹配TRACE信息。(3)設置Layout可以選擇不設置、TTCCLayout、或PatternLayout如果不設置,會輸出簡單格式的log信息。設置TTCCLayout如下所示: log4cplus.appender.ALL_MSGS.layout=log4cplus::TTCCLayout設置PatternLayout如下所示: log4cplus.appender.append_1.layout=log4cplus::PatternLayout log4cplus.appender.append_1.layout.ConversionPattern=%d{%m/%d/%y %H:%M:%S,%Q} [%t] %-5p - %m%n2.logger的配置語法包括rootLogger和non-root logger。對於rootLogger來說: log4cplus.rootLogger=[LogLevel], appenderName, appenderName, ...對於non-root logger來說: log4cplus.logger.logger_name=[LogLevel|INHERITED], appenderName, appenderName, ...腳本方式使用起來非常簡單,只要首先加載配置即可(urconfig.properties是自行定義的配置文件):PropertyConfigurator::doConfigure("urconfig.properties");下面我們通過例子體會一下log4cplus強大的基於腳本過濾log信息的功能。〖例8〗/**//* * urconfig.properties */log4cplus.rootLogger=TRACE, ALL_MSGS, TRACE_MSGS, DEBUG_INFO_MSGS, FATAL_MSGS log4cplus.appender.ALL_MSGS=log4cplus::RollingFileAppenderlog4cplus.appender.ALL_MSGS.File=all_msgs.loglog4cplus.appender.ALL_MSGS.layout=log4cplus::TTCCLayout log4cplus.appender.TRACE_MSGS=log4cplus::RollingFileAppenderlog4cplus.appender.TRACE_MSGS.File=trace_msgs.loglog4cplus.appender.TRACE_MSGS.layout=log4cplus::TTCCLayoutlog4cplus.appender.TRACE_MSGS.filters.1=log4cplus::spi::LogLevelMatchFilterlog4cplus.appender.TRACE_MSGS.filters.1.LogLevelToMatch=TRACElog4cplus.appender.TRACE_MSGS.filters.1.AcceptOnMatch=truelog4cplus.appender.TRACE_MSGS.filters.2=log4cplus::spi::DenyAllFilter log4cplus.appender.DEBUG_INFO_MSGS=log4cplus::RollingFileAppenderlog4cplus.appender.DEBUG_INFO_MSGS.File=debug_info_msgs.loglog4cplus.appender.DEBUG_INFO_MSGS.layout=log4cplus::TTCCLayoutlog4cplus.appender.DEBUG_INFO_MSGS.filters.1=log4cplus::spi::LogLevelRangeFilterlog4cplus.appender.DEBUG_INFO_MSGS.filters.1.LogLevelMin=DEBUGlog4cplus.appender.DEBUG_INFO_MSGS.filters.1.LogLevelMax=INFOlog4cplus.appender.DEBUG_INFO_MSGS.filters.1.AcceptOnMatch=truelog4cplus.appender.DEBUG_INFO_MSGS.filters.2=log4cplus::spi::DenyAllFilter log4cplus.appender.FATAL_MSGS=log4cplus::RollingFileAppenderlog4cplus.appender.FATAL_MSGS.File=fatal_msgs.loglog4cplus.appender.FATAL_MSGS.layout=log4cplus::TTCCLayoutlog4cplus.appender.FATAL_MSGS.filters.1=log4cplus::spi::StringMatchFilterlog4cplus.appender.FATAL_MSGS.filters.1.StringToMatch=FATALlog4cplus.appender.FATAL_MSGS.filters.1.AcceptOnMatch=truelog4cplus.appender.FATAL_MSGS.filters.2=log4cplus::spi::DenyAllFilter /**//* * main.cpp */#include <log4cplus/logger.h>#include <log4cplus/configurator.h>#include <log4cplus/helpers/stringhelper.h> using namespace log4cplus; static Logger logger = Logger::getInstance("log"); void printDebug(){ LOG4CPLUS_TRACE_METHOD(logger, "::printDebug()"); LOG4CPLUS_DEBUG(logger, "This is a DEBUG message"); LOG4CPLUS_INFO(logger, "This is a INFO message"); LOG4CPLUS_WARN(logger, "This is a WARN message"); LOG4CPLUS_ERROR(logger, "This is a ERROR message"); LOG4CPLUS_FATAL(logger, "This is a FATAL message");}int main(){ Logger root = Logger::getRoot(); PropertyConfigurator::doConfigure("urconfig.properties"); printDebug(); return 0;}運行結果:1. all_msgs.log 10-17-04 14:55:25,858 [1075298944] TRACE log <> - ENTER: ::printDebug() 10-17-04 14:55:25,871 [1075298944] DEBUG log <> - This is a DEBUG message 10-17-04 14:55:25,873 [1075298944] INFO log <> - This is a INFO message 10-17-04 14:55:25,873 [1075298944] WARN log <> - This is a WARN message 10-17-04 14:55:25,874 [1075298944] ERROR log <> - This is a ERROR message 10-17-04 14:55:25,874 [1075298944] FATAL log <> - This is a FATAL message 10-17-04 14:55:25,875 [1075298944] TRACE log <> - EXIT: ::printDebug()2. trace_msgs.log 10-17-04 14:55:25,858 [1075298944] TRACE log <> - ENTER: ::printDebug() 10-17-04 14:55:25,875 [1075298944] TRACE log <> - EXIT: ::printDebug()3. debug_info_msgs.log 10-17-04 14:55:25,871 [1075298944] DEBUG log <> - This is a DEBUG message 10-17-04 14:55:25,873 [1075298944] INFO log <> - This is a INFO message4. fatal_msgs.log 10-17-04 14:55:25,874 [1075298944] FATAL log <> - This is a FATAL message本部分詳細介紹瞭如何有選擇地控制log信息的輸出,最後一部分我們將介紹一下多線程、 和C/S模式下該如何操作,順便提一下NDC的概念。log4cplus在很多方面做的都很出色,但是使用過程有些地方感覺不爽。在繼續吹捧之前我先把不爽之處 稍微提一提,然後繼續介紹關於線程和套接字的知識。
### 一些可以改進之處 ###1. 用戶自定義LogLevel的實現機制不夠開放在第五篇中曾經介紹過如何實現用戶自行定義LogLevel,爲了實現比較理想的效果,甚至還需要改log4cplus 的源代碼。:(2. 生成Logger對象的機制可以改進我在使用時候,經常需要在不同的文件、函數中操作同一個logger,雖然log4cplus實現了樹狀存儲以及根據 名稱生成Logger,卻沒有充分利用這樣的特點確保同一個名稱對應的logger對象的唯一性,比如以下代碼:... ... Logger logger1 = Logger::getInstance("test"); Logger logger2 = Logger::getInstance("test");Logger * plogger1 = &logger1; Logger * plogger2 = &logger2;std::cout << "plogger1: " << plogger1 << std::endl << "plogger2: " << plogger2 << std::endl; ... ... 運行結果:plogger1: 0xbfffe5a0 plogger2: 0xbfffe580從結果可以看出,明明是同一個Logger,但每次調用都會產生一個Logger副本,雖然結果是正確的(因爲將存 儲和操作分開了),但是資源有些浪費,我看了一下log4cplus的代碼,其實可以按照如下方式實現(示意性 的):#include <iostream>#include <string>#include <map>
/**//* forward declaration */class Logger;
class LoggerContainer{public:
~LoggerContainer();
Logger * getinstance(const std::string & strLogger);
private:
typedef std::map<:string,> LoggerMap; LoggerMap loggerPtrs;};
class Logger{public: Logger() {std::cout << "ctor of Logger " << std::endl; } ~Logger() {std::cout << "dtor of Logger " << std::endl; }
static Logger * getInstance( const std::string & strLogger) { static LoggerContainer defaultLoggerContainer; return defaultLoggerContainer.getinstance(strLogger); }};
LoggerContainer::~LoggerContainer(){ /**//* release all ptr in LoggerMap */ LoggerMap::iterator itr = loggerPtrs.begin();
for( ; itr != loggerPtrs.end(); ++itr ) { delete (*itr).second; }
}
Logger * LoggerContainer::getinstance(const std::string & strLogger){ LoggerMap::iterator itr = loggerPtrs.find(strLogger);
if(itr != loggerPtrs.end()) { /**//* logger exist, just return it */ return (*itr).second; } else { /**//* return a new logger */ Logger * plogger = new Logger(); loggerPtrs.insert(std::make_pair(strLogger, plogger));
return plogger; }}
int main(){ Logger * plogger1 = Logger::getInstance("test"); Logger * plogger2 = Logger::getInstance("test");
std::cout << "plogger1: " << plogger1 << std::endl << "plogger2: " << plogger2 << std::endl;
return 0;}運行結果:ctor of Logger plogger1: 0x804fc30 plogger2: 0x804fc30 dtor of Logger這裏的LoggerContainer相當於log4cplus中的Hierarchy類,結果可以看出,通過同一個名稱可以獲取相同的 Logger實例。還有一些小毛病比如RollingFileAppender和DailyRollingFileAppender的參數輸入順序可以調整成統一方式 等等,就不細說了。本部分提到了使用log4cplus時候感覺不爽的地方,最後一部分將介紹一下log4cplus中線程和套接字實現情況經過短暫的熟悉過程,log4cplus已經被成功應用到了我的項目中去了,效果還不錯,:)除了上文提及的 功能之外,下面將介紹log4cplus提供的線程和套接字的使用情況。
### NDC ###首先我們先了解一下log4cplus中嵌入診斷上下文(Nested Diagnostic Context),即NDC。對log系統而言, 當輸入源可能不止一個,而只有一個輸出時,往往需要分辯所要輸出消息的來源,比如服務器處理來自不同 客戶端的消息時就需要作此判斷,NDC可以爲交錯顯示的信息打上一個標記(stamp), 使得辨認工作看起來 比較容易些,呵呵。這個標記是線程特有的,利用了線程局部存儲機制,稱爲線程私有數據(Thread-specific Data,或TSD)。 看了一下源代碼,相關定義如下,包括定義、初始化、獲取、設置和清除操作:linux pthread# define LOG4CPLUS_THREAD_LOCAL_TYPE pthread_key_t* # define LOG4CPLUS_THREAD_LOCAL_INIT ::log4cplus::thread::createPthreadKey() # define LOG4CPLUS_GET_THREAD_LOCAL_VALUE( key ) pthread_getspecific(*key) # define LOG4CPLUS_SET_THREAD_LOCAL_VALUE( key, value ) pthread_setspecific(*key, value) # define LOG4CPLUS_THREAD_LOCAL_CLEANUP( key ) pthread_key_delete(*key)win32# define LOG4CPLUS_THREAD_LOCAL_TYPE DWORD # define LOG4CPLUS_THREAD_LOCAL_INIT TlsAlloc() # define LOG4CPLUS_GET_THREAD_LOCAL_VALUE( key ) TlsGetValue(key) # define LOG4CPLUS_SET_THREAD_LOCAL_VALUE( key, value ) \ TlsSetValue(key, static_cast(value)) # define LOG4CPLUS_THREAD_LOCAL_CLEANUP( key ) TlsFree(key)使用起來比較簡單,在某個線程中:NDC& ndc = log4cplus::getNDC(); ndc.push("ur ndc string"); LOG4CPLUS_DEBUG(logger, "this is a NDC test");... ... ndc.pop(); ... ... LOG4CPLUS_DEBUG(logger, "There should be no NDC..."); ndc.remove(); 當設定輸出格式(Layout)爲TTCCLayout時,輸出如下:10-21-04 21:32:58, [3392] DEBUG test - this is a NDC test 10-21-04 21:32:58, [3392] DEBUG test <> - There should be no NDC...也可以在自定義的輸出格式中使用NDC(用%x) ,比如:... ... std::string pattern = "NDC:[%x] - %m %n"; std::auto_ptr _layout(new PatternLayout(pattern));... ... LOG4CPLUS_DEBUG(_logger, "This is the FIRST log message...") NDC& ndc = log4cplus::getNDC(); ndc.push("ur ndc string"); LOG4CPLUS_WARN(_logger, "This is the SECOND log message...") ndc.pop(); ndc.remove(); ... ... 輸出如下:NDC:[] - This is the FIRST log message... NDC:[ur ndc string] - This is the SECOND log message...另外一種更簡單的使用方法是在線程中直接用NDCContextCreator:NDCContextCreator _first_ndc("ur ndc string"); LOG4CPLUS_DEBUG(logger, "this is a NDC test") 不必顯式地調用push/pop了,而且當出現異常時,能夠確保push與pop的調用是匹配的。### 線程 ###線程是log4cplus中的副產品, 而且僅作了最基本的實現,使用起來也異常簡單,只要且必須要 在派生類中重載run函數即可:class TestThread : public AbstractThread { public:virtual void run();};void TestThread::run() { /* do sth. */ ... ...}log4cplus的線程沒有考慮同步、死鎖,有互斥,實現線程切換的小函數挺別緻的:void log4cplus::thread::yield() { #if defined(LOG4CPLUS_USE_PTHREADS) ::sched_yield(); #elif defined(LOG4CPLUS_USE_WIN32_THREADS) ::Sleep(0); #endif }### 套接字 ###套接字也是log4cplus中的副產品,在namespace log4cplus::helpers中,實現了C/S方式的日誌記錄。1. 客戶端程序需要做的工作:/* 定義一個SocketAppender類型的掛接器 */ SharedAppenderPtr _append(new SocketAppender(host, 8888, "ServerName"));/* 把_append加入到logger中 */ Logger::getRoot().addAppender(_append);/* SocketAppender類型不需要Layout, 直接調用宏就可以將信息發往loggerServer了 */ LOG4CPLUS_INFO(Logger::getRoot(), "This is a test: ")【注】 這裏對宏的調用其實是調用了SocketAppender::append,裏面有一個數據傳輸約定,即先發送 一個後續數據的總長度,然後再發送實際的數據:... ...SocketBuffer buffer = convertToBuffer(event, serverName); SocketBuffer msgBuffer(LOG4CPLUS_MAX_MESSAGE_SIZE);msgBuffer.appendSize_t(buffer.getSize()); msgBuffer.appendBuffer(buffer); ... ...2. 服務器端程序需要做的工作:/* 定義一個ServerSocket */ ServerSocket serverSocket(port);/* 調用accept函數創建一個新的socket與客戶端連接 */ Socket sock = serverSocket.accept();此後即可用該sock進行數據read/write了,形如:SocketBuffer msgSizeBuffer(sizeof(unsigned int));if(!clientsock.read(msgSizeBuffer)) { return; }unsigned int msgSize = msgSizeBuffer.readInt();SocketBuffer buffer(msgSize);if(!clientsock.read(buffer)) { return; }爲了將讀到的數據正常顯示出來,需要將SocketBuffer存放的內容轉換成InternalLoggingEvent格式:spi::InternalLoggingEvent event = readFromBuffer(buffer);然後輸出: Logger logger = Logger::getInstance(event.getLoggerName()); logger.callAppenders(event);【注】 read/write是按照阻塞方式實現的,意味着對其調用直到滿足了所接收或發送的個數才返回。