日志系統的另一個基本功能就是能夠讓使用者按照自己的意愿來控制什么時候,哪些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: DEBUG
Setting test.subtest to WARN
FATAL - root: DEBUG
FATAL - test: DEBUG
FATAL - test.subtest: WARN
Setting test.subtest to TRACE
FATAL - root: DEBUG
FATAL - test: TRACE
FATAL - test.subtest: WARN
Setting test.subtest to NO_LEVEL
FATAL - root: DEBUG
FATAL - test: TRACE
FATAL - test.subtest: TRACE
create 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 <> - info
trace-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 <> - info
debug-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 <> - info
info-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 <> - info
warn-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 <> - info
error-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 <> - info
fatal-log and above allowed
10-17-04 10:11:40,600 [1075298944] FATAL root <> - info
log 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%n
2.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 message
4. 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是按照阻塞方式實現的,意味著對其調用直到滿足了所接收或發送的個數才返回。