ACE是一個很強大的網路中間件
我們就先從日志開始說起
ACE對日志處理就行了封裝,而且封裝格式是printf的格式,而並非cout格式的
而且ACE日志輸出的本身格式很類似log4cpp的日志格式
對日志有一個日志等級限制,然后就是類似C,printf的字符串輸出
ACE_DEBUG((serverity,formatting-args));
ACE_ERROR((servirity,formatting-args));
ACE一共定義了9種嚴重級別
LM_TRACE 指示函數調用次序的消息
LM_DEBUG 調試信息
LM_INFO 消息含有通常只在程序調試時使用的信息
LM_NOTICE 不是出錯的情況,而是可能需要特別處理的情況
LM_WARNING 警告消息
LM_ERROR 錯誤消息
一個簡單的例子:
#define ACE_NTRACE 0
#include "ace/Log_Msg.h"
#ifdef _DEBUG
#pragma comment(lib,"ACEd.lib")
#else
#pragma comment(lib,"ACE.lib")
#endif
void foo(void);
int ACE_TMAIN(int,ACE_TCHAR *[])
{
ACE_TRACE(ACE_TEXT("main"));
ACE_LOG_MSG->priority_mask(LM_NOTICE|LM_TRACE,ACE_Log_Msg::PROCESS);
ACE_DEBUG((LM_INFO,ACE_TEXT("%I Hi Mom\n")));
foo();
ACE_DEBUG((LM_INFO,ACE_TEXT("%I Goodnight\n")));
getchar();
return 0;
}
void foo(void)
{
ACE_TRACE(ACE_TEXT("foo"));
ACE_DEBUG((LM_INFO,ACE_TEXT("%IHowdy pardner\n")));
}
注意:
1,可以直接在工程中,添加依賴庫的路徑來替換剛開始的pragma宏操作
2,如果想ACE_TRACE輸出,需要在開始定義#define ACE_NTRACE 0,而且順序不能反了
3,設置日志等級過濾的時候,需要注意debug情況
ACE格式輸出控制如下:
/**
* Format a message to the thread-safe ACE logging mechanism. Valid
* options (prefixed by '%', as in printf format strings) include:
* - 'A': print an ACE_timer_t value (which could be either double
* or ACE_UINT32.)
* - 'a': abort the program at this point abruptly.
* - 'b': print a ssize_t value
* - 'B': print a size_t value
* - 'c': print a character
* - 'C': print a char* character string (also see s and W)
* - 'i', 'd': print a decimal number
* - 'I': indent according to nesting depth (obtained from
* ACE_Trace::get_nesting_indent()).
* - 'e', 'E', 'f', 'F', 'g', 'G': print a double
* - 'l': print line number where an error occurred.
* - 'M': print the name of the priority of the message.
* - 'm': return the message corresponding to errno value, e.g., as
* done by strerror()
* - 'N': print file name where the error occurred.
* - 'n': print the name of the program (or "<unknown>" if not set)
* - 'o': print as an octal number
* - 'P': print out the current process id
* - 'p': print out the appropriate errno message from sys_errlist,
* e.g., as done by perror()
* - 'Q': print out the uint64 number
* - 'q': print out the int64 number
* - '@': print a void* pointer (in hexadecimal)
* - 'r': call the function pointed to by the corresponding argument
* - 'R': print return status
* - 'S': print out the appropriate signal message corresponding
* to var-argument, e.g., as done by strsignal()
* - 's': prints a ACE_TCHAR* character string (also see C and W)
* - 'T': print timestamp in hour:minute:sec:usec format (plain option,
* i.e. without any flags, prints system supplied timestamp;
* with '#' flag added expects ACE_Time_Value* in argument list)
* - 'D': print timestamp as Weekday Month day year hour:minute:sec.usec
* (plain option, i.e. without any flags, prints system supplied
* timestamp; with '#' flag added expects ACE_Time_Value* in
* argument list)
* - 't': print thread id (1 if single-threaded)
* - 'u': print as unsigned int
* - 'w': prints a wide character
* - 'W': prints a wchar_t* character string (also see C and s)
* - 'x': print as a hex number
* - 'X': print as a hex number
* - 'z': print an ACE_OS::WChar character
* - 'Z': print an ACE_OS::WChar character string
* - ':': print a time_t value as an integral number
* - '%': print out a single percent sign, '%'
* - '?': print out stack trace (see Stack_Trace.h header comments)
*/
其他的一些宏:
# define ACE_HEX_DUMP(X) do {} while (0)
#endif
#if !defined (ACE_RETURN)
# define ACE_RETURN(Y) do { return (Y); } while (0)
#endif
#if !defined (ACE_ERROR_RETURN)
# define ACE_ERROR_RETURN(X, Y) return (Y)
#endif
#if !defined (ACE_ERROR_BREAK)
# define ACE_ERROR_BREAK(X) { break; }
#endif
#if !defined (ACE_ERROR)
# define ACE_ERROR(X) do {} while (0)
#endif
#if !defined (ACE_DEBUG)
# define ACE_DEBUG(X) do {} while (0)
#endif
#if !defined (ACE_ERROR_INIT)
# define ACE_ERROR_INIT(VALUE, FLAGS)
ACE_RETURN(value) 不打印消息,發出調用的函數會返回,返回值為value,op_status被設置為value, 開關宏為:ACE_NLOGGING
ACE_ERROR_RETURN((level,string,...),value) 在所要求的級別level上記錄string,然后發出函數返回,返回值為value,op_states被設置為value ,
開關宏為ACE_NLOGGING
ACE_ERROR((level,string,...)) 把op_states設為-1,並且在所要求的級別level上記錄string
ACE_DEBUG((level,string,...)) 把op_status設置為0,並且在所要求的級別level上記錄string
ACE_ERROR_INIT(value,flags) 把op_status設為value,把日志記錄器的選項標志為flags
ACE_ERROR_BREAK((level,string,...)) 調用ACE_ERROR,然后執行break(while,for)
ACE_TRACE(string) 在ACE_TRACE出現之處顯示文件名,行號,以及string,在退出其作用域時顯示“Leaving 'string'”
ACE日志工作方式:
ACE_Log_Msg類上實現了ACE的日志消息格式化功能。ACE為每個新派生的線程自動維護該線程專有的ACE_Log_Msg類的單體實例,其中也包括主線程。要獲取線程的ACE_Log_Msg單體實例的指針,可以使用ACE_LOG_MSG這種快捷方式。為了在正確的ACE_Log_Msg實例上發出方法調用,所有的ACE日志宏都使用了ACE_LOG_MSG。你幾乎沒有理由直接實例化ACE_Log_Msg。ACE自動為每個新派生的線程創建一個新的ACE_Log_Msg實例,從而讓每個線程都有各自的日志輸出
我們可以用ACE_Log_Msg::priority_mask()方法設置輸出所處的日志嚴重級別。每一個級別都有一個位掩碼對應,所以各個級別可以組合在一起。
u_long priority_mask(MASK_TYPE = THREAD); u_long priority_mask(u_long,MASK_TYPE = THREAD);
第一個函數用於讀取嚴重性掩碼,第二個是改變嚴重性掩碼,同時返回舊的 ,以備以后恢復用
MASK_TYPE是下面兩種情況之一:
1,ACE_Log_Msg::PROCESS: 指定PROCESS,獲取或設置進程范圍的掩碼,會影響所有ACE_Log_Msg實例的日志嚴重性
2,ACE_Log_Msg::THREAD: 每個ACE_Log_Msg實例有自己的嚴重性掩碼,指定THREAD可以獲取或設置該掩碼。
其實THREAD嚴格來說明名有點問題,本質是指的priority_mask()方法所針對的ACE_Log_Msg實例,而除了ACE為每個線程創建ACE_Log_Msg實例,還可以創建其他ACE_Log_Msg實例。但是這是一種相對而言很罕見的做法,所有我們通常就把ACE_Log_Msg實例視為線程專用的
在評估日志消息的嚴重性時,ACE_Log_Msg即檢查進程范圍的嚴重性掩碼,也檢查每個實例嚴重性的掩碼。如果在這兩個掩碼中的任何一個啟用了該消息的嚴重級別,這條消息就會記入日志。
默認情況下,進程級的,每個位都是1,而實例級的,每個位都是0,所有無論消息具有何種嚴重性,它都會被計入日志。
要讓每個線程自己決定把哪些嚴重級別記入日志,把進程范圍的掩碼設為0,並允許每個線程設置自己的每個實例嚴重性掩碼
例如,設置當前線程只啟用 LM_DEBUG 和 LM_NOTICE嚴重性:
ACE_LOG_MSG->priority_mask(0,ACE_Log_Msg::PROCESS); ACE_LOG_MSG->priority_mask(LM_DEBUG|LM_NOTICE,ACE_Log_Msk::THREAD);
當需要在ACE_Log_Msg實例上設置個別的嚴重性掩碼時,ACE_Log_Msg維護的第三個掩碼十分重要。默認的每個實例嚴重性掩碼被用於初始化每個ACE_Log_Msg實例的嚴重性掩碼。這個默認掩碼一開始全部為0(沒有啟用任何嚴重級別)。因為每個ACE_Log_Msg實例的嚴重性掩碼是在該實例創建時根據這個默認值設置的,你可以派生成組的線程之前改變他們的默認值。這使你的應用的線程派生部分具備了日志記錄策略,從而能減少線程設置自己的嚴重級別的需求,盡管每個線程仍然可以隨時改變其ACE_Log_Msg實例的掩碼。
但是,看下面例子:
ACE_LOG_MSG->priority_mask(0,ACE_Log_Msg::PROCESS); ACE_LOG_MSG::enable_debug_messages(); ACE_Thread_Manager::instance()->spawn(service); ACE_Log_Msg::disable_debug_messages(); ACE_Thread_Manager::instance()->spawn_n(3,worker);
在例子中,進程范圍的嚴重性掩碼被設為0,這意味着,每個ACE_Log_Msg實例的掩碼會完全決定它的那些嚴重級別是被啟用的。執行service()函數的線程的LM_DEBUG嚴重級別將是啟用的,但是執行worker()函數的線程則不是這樣。
static void disable_debug_messages (ACE_Log_Priority priority = LM_DEBUG); static void enable_debug_messages (ACE_Log_Priority priority = LM_DEBUG);
雖然源碼中這兩個函數都使用了默認參數,但是實際上,它的處理過程,我們可以推測出,我們也可以用它改變其他嚴重性級別(非LM_DEBUG)
與替換指定掩碼的priority_mask()方法不同,enable_debug_messages()和disable_debug_message()方法會分別在發出調用的線程的“per instance”掩碼和默認的“per instance” 嚴重性掩碼中加上和減去指定的嚴重性位集(serverity bits)
當然,你可以隨時使用任何消息嚴重級別。但是,要注意為你的每條消息指定合理的級別。然后在運行時,你可以用priority_mask()方法啟用或禁用你關注的消息。這樣,你可以“過度裝備”你的代碼,然后只啟用那些在特定時刻有用的級別
ACE_Log_Msg擁有一組豐富的方法,可以用於記錄你應用的當前狀態。這些方法大多數都同時具有accessor或mutator型構
例如有兩個op_status()方法:
int op_status(void); void op_status(int status));
盡管這些方法往往是通過ACE日志宏間接調用的,你也可以直接使用它們
ACE_Log_Msg常用方法
op_status 當前函數的返回值。-1指示出錯情況
errnum 當前的errno值
linenum 所生成消息所在行的行號
file 所生成消息所在文件的名稱
msg 要發送給日志輸出目標的消息
inc 增加嵌套深度,返回先前的值
dec 減少嵌套深度,返回新值
trace_depth 當前嵌套深度
start_tracing
stop_tracing
tracing_enabled 啟用/禁用/查詢/當前的ACE_Log_Msg實例的跟蹤狀態。線程的ACE_LOG_MSG單體的跟蹤狀態決定ACE_Trace對象是否生產日志消息
priority_mask 獲取/設置要被計入日志的消息的嚴重級別集--在實例或進城級
log_priority_enabled 如果所請求的優先級已經啟用,返回非零
set 同時設置行號,文件名,op_status以及若干其他特征
conditional_set 設置下一條日志消息的行號,文件名,op_status以及errnum值;但是,只有當下一條日志消息嚴重級別已啟用時,它們才會生效
另外,我們可以定制我們自己的宏,添加我們的前綴,或者加上適當的縮進:
#define DEBUG_PREFIX ACE_TEXT("DEBUG%I")
#define INFO_PREFIX ACE_TEXT("INFO%I")
#define NOTICE_PREFIX ACE_TEXT("NOTICE%I")
#define WARNING_PREFIX ACE_TEXT("WARNING%I")
#define ERROR_PREFIX ACE_TEXT("ERROR%I")
#define CRITICAL_PREFIX ACE_TEXT("CRITICAL%I")
#define ALERT_PREFIX ACE_TEXT("ALERT%I")
#define EMERGENCY_PREFIX ACE_TEXT("EMERGENCY%I")
#define MY_DEBUG(FMT,...) \ ACE_DEBUG((LM_DEBUG,DEBUG_PREFIX FMT __VA_ARGS__)) #define MY_INFO(FMT,...) \ ACE_DEBUG((LM_INFO,INFO_PREFIX FMT __VA_ARGS__)) #define MY_NOTICE(FMT,...) \ ACE_DEBUG((LM_NOTICE,NOTICE_PREFIX FMT __VA_ARGS__)) #define MY_WARNING(FMT,...) \ ACE_DEBUG((LM_WARNING,WARNING_PREFIX FMT __VA_ARGS__)) #define MY_ERROR(FMT,...) \ ACE_DEBUG((LM_ERROR,ERROR_PREFIX FMT __VA_ARGS__)) #define MY_CRITICAL(FMT,...) \ ACE_DEBUG((LM_CRITICAL,CRITICAL_PREFIX FMT __VA_ARGS__)) #define MY_ALERT(FMT,...) \ ACE_DEBUG((LM_ALERT,ALERT_PREFIX FMT __VA_ARGS__)) #define MY_EMERGENCY(FMT,...) ACE_DEBUG((LM_EMERGENCY,EMERGENCY_PREFIX FMT __VA_ARGS__))
我們可以加上#ifdef宏,來更靈活的使用我們自己的宏
__VA_ARGS__ 是一個可變參數的宏,很少人知道這個宏,這個可變參數的宏是新的C99規范中新增的,目前似乎只有gcc支持(VC6.0的編譯器不支持)。宏前面可以加上##,作用在於,當可變參數的個數為0時,這里的##起到把前面多余的","去掉的作用,否則會編譯出錯, 你可以試試
看例子:
#define ACE_NTRACE 0 #include "ace/Log_Msg.h"
#define DEBUG_PREFIX ACE_TEXT("DEBUG%I")
#define INFO_PREFIX ACE_TEXT("INFO%I")
#define NOTICE_PREFIX ACE_TEXT("NOTICE%I")
#define WARNING_PREFIX ACE_TEXT("WARNING%I")
#define ERROR_PREFIX ACE_TEXT("ERROR%I")
#define CRITICAL_PREFIX ACE_TEXT("CRITICAL%I")
#define ALERT_PREFIX ACE_TEXT("ALERT%I")
#define EMERGENCY_PREFIX ACE_TEXT("EMERGENCY%I")
#define MY_DEBUG(FMT,...) \ ACE_DEBUG((LM_DEBUG,DEBUG_PREFIX FMT __VA_ARGS__)) #define MY_INFO(FMT,...) \ ACE_DEBUG((LM_INFO,INFO_PREFIX FMT __VA_ARGS__)) #define MY_NOTICE(FMT,...) \ ACE_DEBUG((LM_NOTICE,NOTICE_PREFIX FMT __VA_ARGS__)) #define MY_WARNING(FMT,...) \ ACE_DEBUG((LM_WARNING,WARNING_PREFIX FMT __VA_ARGS__)) #define MY_ERROR(FMT,...) \ ACE_DEBUG((LM_ERROR,ERROR_PREFIX FMT __VA_ARGS__)) #define MY_CRITICAL(FMT,...) \ ACE_DEBUG((LM_CRITICAL,CRITICAL_PREFIX FMT __VA_ARGS__)) #define MY_ALERT(FMT,...) \ ACE_DEBUG((LM_ALERT,ALERT_PREFIX FMT __VA_ARGS__)) #define MY_EMERGENCY(FMT,...)\ ACE_DEBUG((LM_EMERGENCY,EMERGENCY_PREFIX FMT __VA_ARGS__)) #ifdef _DEBUG #pragma comment(lib,"ACEd.lib")
#else
#pragma comment(lib,"ACE.lib")
#endif
void foo(void); int ACE_TMAIN(int,ACE_TCHAR *[]) { ACE_TRACE(ACE_TEXT("main")); // ACE_LOG_MSG->priority_mask(LM_NOTICE|LM_TRACE,ACE_Log_Msg::PROCESS);
MY_DEBUG(ACE_TEXT("%I Hi Mom\n")); foo(); MY_DEBUG(ACE_TEXT("%I Goodnight\n")); getchar(); return 0; } void foo(void) { ACE_TRACE(ACE_TEXT("foo")); MY_DEBUG(ACE_TEXT("%IHowdy pardner\n")); }
結果:
但是__VA_ARGS__僅能在GNU編譯器下可以正常工作,(現在vs2012等都支持,對於不支持的,例如vc6.0)如果想在別處,要采用特定的方法
這種代碼閱讀性稍微差一些的方法:
#define MY_DEBUG LM_DEBUG, ACE_TEXT("DEBUG%I")
#define MY_INFO LM_INFO, ACE_TEXT("INFO%I")
#define MY_NOTICE LM_NOTICE, ACE_TEXT("NOTICE%I")
#define MY_WARNING LM_WARNING, ACE_TEXT("WARNING%I")
#define MY_ERROR LM_ERROR, ACE_TEXT("ERROR%I")
#define MY_CRITICAL LM_CRITICAL, ACE_TEXT("CRITICAL%I")
#define MY_ALERT LM_ALERT, ACE_TEXT("ALERT%I")
#define MY_EMERGENCY LM_EMERGENCY, ACE_TEXT("EMERGENCY%I")
我們可以這樣使用:
ACE_DEBUG((MY_DEBUG ACE_TEXT("Hi Mom\n"))); ACE_DEBUG((MY_DEBUG ACE_TEXT("Good Morning\n")));
現在我們封裝一個ACE_Trace的,現實函數退出處的行號。默認的ACE_Trace沒有做這些事,因為源碼沒法擴展,因此要想實現,我們必須重新實現
並且裁剪源碼
class Trace { public: Trace(const ACE_TCHAR *prefix, const ACE_TCHAR *name, int line, const ACE_TCHAR *file) { this->prefix_ = prefix; this->name_ = name; this->line_ = line; this->file_ = file; ACE_Log_Msg *lm = ACE_LOG_MSG; if(lm->tracing_enabled() && lm->trace_active() == 0) { lm->trace_active(1); ACE_DEBUG((LM_TRACE, ACE_TEXT("%s%*s(%t) calling %s in file '%s'") ACE_TEXT(" on line %d\n"), this->prefix_, Trace::nesting_indent_ * lm->inc(), ACE_TEXT(""), this->name_, this->file_, this->line_)); lm->trace_active(0); } } void setLine(int line) { this->line_ = line; } ~Trace(void) { ACE_Log_Msg *lm = ACE_LOG_MSG; if(lm->tracing_enabled() && lm->trace_active() == 0) { lm->trace_active(1); ACE_DEBUG((LM_TRACE, ACE_TEXT("%s%*s(%t) leaving %s in file '%s'") ACE_TEXT(" on line %d\n"), this->prefix_, Trace::nesting_indent_ * lm->dec(), ACE_TEXT(""), this->name_, this->file_, this->line_)); lm->trace_active(0); } } private: enum {nesting_indent_ = 3}; const ACE_TCHAR *prefix_; const ACE_TCHAR *name_; const ACE_TCHAR *file_; int line_; };
Trace是ACE_Trace的簡化版本。
有了新的Trace類,我們可以創建一組簡單的宏,使用這個新類來在我們的代碼中實現函數跟蹤:
#define TRACE_PREFIX ACE_TEXT("TRACE ")
#if (ACE_NTRACE == 1) # define TRACE(X) # define TRACE_RETURN(V) # define TRACE_RETURN_VOID() #else # define TRACE(X) \ Trace ____(TRACE_PREFIX,ACE_TEXT(X),__LINE__,ACE_TEXT(__FILE__)) # define TRACE_RETURN(V) \ do {____.setLine(__LINE__); return V;} while(0) # define TRACE_RETURN_VOID() \ do {____.setLine(__LINE__);}while(0)
ACE的日志設施的默認接收槽式標准錯誤流。但是因為一個東西的存在,我們可以實現不同的槽,它就是---重定向
我們可以通過重定向把日志推送到其他兩種常用的有用目標:
1,系統日志記錄器(UNIX syslog 或 NT EventLog)
2,程序員指定的輸出流,比如文件
雖然我們最常用的日志是輸出的標准錯誤流,常用到以至於ACE日志消息的默認接收槽就是標准錯誤
但是有時候,我們並不一定完全要求日志輸出到stderr,同時還要求輸出到其他設備,比如日志比較大的時候輸出到文件。
但是這個時候需要我們顯式指明stderr
ACE_LOG_MSG->open(argv[0],ACE_Log_Msg::STDERR);
或是
ACE_DEBUG((LM_DEBUG,ACE_TEXT("%IHi Mom\n"))); ACE_LOG_MSG->set_flags(ACE_Log_Msg::STDERR);
注意:如果選擇第二種做法,必須要調用clr_flags()禁用其他任何輸出目的地。任何在set_flags()之后產生的消息都會被定向到STDERR,除非你調用clr_flags()加以禁止
這兩個方法的完整型構式:
void set_flags(unsigned long f); void clr_flags(unsigned long f);
下面列出有效的ACE_Log_Msg標志值
STDERR 把消息寫往STDERR
LOGGER 把消息寫往本地的客戶日志記錄器看守(daemon)
OSTREAM 把消息寫往指定的輸出流
MSG_CALLBACK 把消息寫往回調對象
VERBOSE 在每條消息前面加上程序名、時間戳、主機名、進程ID以及消息優先級
VERBOSE_LITE 在每條消息前面加上時間戳和消息優先級
SILENT 不打印任何消息
SYSLOG 把消息寫往系統的事件日志
CUSTOM 把消息寫往用戶提供的后端
大多數的現代OS都支持系統日志記錄器。具體實現方式從函數調用庫到網絡看守都有。
總體思想:所有應用都把他們的日志記錄定向到系統日志記錄器,而后者依次又把這些記錄定向到正確的文件或其他可配置的目的地。
例如:Unix類的syslog設施,讓不同類型和不同級別的日志記錄被定向到不同的目的地。這樣做能夠帶來可伸縮性和可配置性的良好結合
ACE_LOG_MSG->open(argv[0],ACE_Log_Msg::SYSLOG,ACE_TEXT("syslogTest"));
注意:不能使用set_flags()來在ACE_Log_Msg實例打開之后啟用syslog輸出,同樣clr_flags()也做不到停止輸出日志到syslog
為了與系統日志記錄器通信,ACE_Log_Msg必須執行一組初始化步驟,這些步驟只有在open()方法中才能完成。在初始化過程中需要用到程序名,該名稱被記錄到syslog中:(第三個參數)。如果在程序啟動時,我們沒有做這項工作,為了通過調用set_flags()獲得我們期望的行為,我們必須在后面做這件事情。於此類似,如果在調用open()方法時沒有指定ACE_Log_Msg::SYSLOG標志,該方法就會適當地關閉任何已有的與系統日志記錄器的連接:
#define ACE_NTRACE 0 #include "ace/Log_Msg.h" #ifdef _DEBUG #pragma comment(lib,"ACEd.lib")
#else
#pragma comment(lib,"ACE.lib")
#endif
void foo(void); int ACE_TMAIN(int,ACE_TCHAR *argv[]) { ACE_TRACE(ACE_TEXT("main")); // ACE_LOG_MSG->priority_mask(LM_NOTICE|LM_TRACE,ACE_Log_Msg::PROCESS);
ACE_DEBUG((LM_DEBUG,ACE_TEXT("%I Hi Mom\n"))); ACE_LOG_MSG->open(argv[0],ACE_Log_Msg::SYSLOG,ACE_TEXT("syslogTest")); foo(); ACE_LOG_MSG->open(argv[0]); ACE_DEBUG((LM_INFO,ACE_TEXT("%I Goodnight\n"))); getchar(); return 0; } void foo(void) { ACE_TRACE(ACE_TEXT("foo")); ACE_DEBUG((LM_INFO,ACE_TEXT("%IHowdy pardner\n"))); }
盡管在應用中多次調用ACE_LOG_MSG->open()看着怪怪的,但是這樣做並沒有任何問題,可以把它看作是重新打開
取決於平台的原生”系統日志記錄器“的性質和功能,在不同的平台上,把日志輸出定向到SYSLOG意味着不同的事情。如果運行時平台不支持任何類型的系統日志記錄器,把輸出定向到SYSLOG沒有任何效果,支持ACE中SYSLOG的平台:
1,Windows NT 4及更高版本, windows 2000,XP:ACE把SYSLOG輸出定向到系統的Event Log。ACE_Log_Msg::open()的第三個參數是一個ACE_TCHAR*字符串。這個參數可選。如果提供了這個參數,它會取代程序名,用作在系統的事件日志中記錄事件時所用的事件源的名稱,ACE消息的嚴重性被映射到EventLog的嚴重性
映射關系看下圖:
2,Unix/Linux:ACE把SYSLOG輸出定向到syslog設施。該設施具有自己的一些與日志設施相關的配置細節,不同於ACE的日志嚴重級別。在默認情況下,ACE的syslog后端會指定使用LOG_USER syslog設施。在編譯時可以改變config.h中的設置ACE_DEFAULT_SYSLOG_FACILITY
在C++中,提供的輸出流(ostream對象)相對於printf函數組有更強的功能,而且他們編寫的代碼可讀性也更好。
通過ACE_Log_Msg::msg_ostream()方法,我們可以提供一個輸出流,讓日志記錄器把我們的信息寫往這個流:
ACE_OSTREAM_TYPE *output = new std::ofstream("ostream.output.test"); ACE_LOG_MSG->msg_ostream(out,1); ACE_LOG_MSG->set_flags(ACE_Log_Msg::OSTREAM); ACE_LOG_MSG->clr_flags(ACE_Log_Msg::STDERR);
注意:通過open()或set_flags()選擇OSTREAM作為輸出,然后調用msg_ostream()之前生成的日志輸出是安全的,如果這樣做,輸出就會消失,因為還沒有制定ostream。我們使用msg_ostream()的兩個參數版本,這不僅會設置ACE_Log_Msg實例要使用ostream,而且還會告訴ACE_Log_Msg,它應該接管該ostream實例的所有權,並且在自己被刪除時刪除該實例。msg_ostream()的單參數版本沒有指定所有權方面的默認行為,所以明確表達你的意願,能夠帶來回報
為什么流類型是ACE_OSTREAM_TYPE,而不是std::ostream。
這是為了提高各種不同規模和能力的平台上的可移植性,ACE采用了又一種設計。
無論有沒有生命std命名空間,都可以定義ACE_OSTREAM_TYPE,對於沒有任何C++ iostream支持的平台,比如,嵌入式環境,ACE_OSTREAM_TYPE可被定為FILE
我們可以輕松的組合這些技術,並通過三種方式分發我們的日志信息:
#include "ace/Log_Msg.h" #include "ace/streams.h" #ifdef _DEBUG #pragma comment(lib,"ACEd.lib") #else #pragma comment(lib,"ACE.lib") #endif int ACE_TMAIN(int,ACE_TCHAR *argv[]) { ACE_LOG_MSG->open(argv[0]); ACE_TRACE(ACE_TEXT("main")); ACE_OSTREAM_TYPE * output = new std::ofstream("ostream.output.test"); ACE_DEBUG((LM_DEBUG,ACE_TEXT("%IThis will go to STDERR\n"))); ACE_LOG_MSG->open(argv[0],ACE_Log_Msg::SYSLOG,ACE_TEXT("syslogTest")); ACE_LOG_MSG->set_flags(ACE_Log_Msg::STDERR); ACE_DEBUG((LM_DEBUG,ACE_TEXT("%IThis goes to STDERR & syslog\n"))); ACE_LOG_MSG->msg_ostream(output,0); ACE_LOG_MSG->set_flags(ACE_Log_Msg::OSTREAM); ACE_DEBUG((LM_DEBUG,ACE_TEXT("%IThis will go to STDERR,") ACE_TEXT("syslog & an ostream\n"))); ACE_LOG_MSG->clr_flags(ACE_Log_Msg::OSTREAM); delete output; getchar(); return 0; }
使用ostream要小心,有一只狡猾的bug在等着你。
在刪除ostream實例output之前,我們清除了ACE_Log_Msg實例上的OSTREAM標志。要記住,當跟蹤實例在main()結束出退出作用域時
main的ACE_TRACE仍必須寫出它的最后一條消息。如果我們只是刪除ostream,而沒有移除OSTREAM標志,ACE_Log_Msg會負責地在已刪除的ostream實例上嘗試寫出最后一條消息,而程序可能會崩潰
我們一直滿足於把我們的日志輸出交給ACE_Log_Msg,由它對消息進行格式化,並定向給配置指定的日志接收槽。在大多數情況下,這樣做都還可以。
但如果我們想要自己處理輸出,這就有點捉足見肘了!在日志輸出到目的地之前,我們可否對其進行檢查甚至修改?
這就需要我們的ACE_log_Msg_Callback閃亮登場了。
使用回調對象相當簡單。遵循下面步驟:
1,從ACE_Log_Msg_Callback派生一個回調類,重新實現下面這個虛函數:
virtual void log(ACE_Log_Recode &log_recode);
2,創建你的新回調類型的一個對象
3,向某個ACE_Log_Msg實例登記該回調對象,把指向你的回調對象的指針傳給ACE_Log_Msg::msg_callback()方法
4,調用ACE_Log_Msg::set_flags(),使輸出開始送往你的回調對象
一旦登記並啟用,只要ACE_Log_Msg::log()被調用,你的回調對象的log()方法就會被調用,並且傳入一個ACE_Log_Recode對象。
結果表明,那正是使用了會產生輸出的ACE日志宏時所發生的事情
但是要注意下面幾點:
1,回調登記和啟用時針對每個ACE_Log_Msg實例的,因此,在某個線程中設置的回調 不會被應用中的其他任何線程使用
2,為你創建的任何線程所創建的ACE_Log_Msg實例都不會繼承回調對象。所以,如果你要在多線程應用中使用回調對象,需要特別注意,給每個線程指定一個適當的回調實例。安全地使用單個對象也是可能的
3,與前面的OSTREAM警告的情況一樣,如果回調實例所登記到ACE_Log_Msg實例仍在使用它,要確保你不會刪除這樣的實例
看一個小小例子:
#define ACE_NTRACE 0 #include "ace/streams.h" #include "ace/Log_Msg.h" #include "ace/Log_Msg_Callback.h" #include "ace/Log_Record.h" #ifdef _DEBUG #pragma comment(lib,"ACEd.lib") #else #pragma comment(lib,"ACE.lib") #endif class CallBack : public ACE_Log_Msg_Callback { public: void log(ACE_Log_Record &log_recode) { log_recode.print(ACE_TEXT(""),0,cerr); log_recode.print(ACE_TEXT(""),ACE_Log_Msg::VERBOSE,cerr); } }; int ACE_TMAIN (int,ACE_TCHAR*[]) { CallBack *callback = new CallBack; ACE_LOG_MSG->set_flags(ACE_Log_Msg::MSG_CALLBACK); ACE_LOG_MSG->clr_flags(ACE_Log_Msg::STDERR); ACE_LOG_MSG->msg_callback(callback); ACE_TRACE(ACE_TEXT("main")); ACE_DEBUG((LM_DEBUG,ACE_TEXT("%IHi Mom\n"))); ACE_DEBUG((LM_DEBUG,ACE_TEXT("%IGoodnight\n"))); getchar(); return 0; }
輸出結果為:
一旦擁有了對ACE_Log_Recode實例的訪問權,就能夠做任何你想做的事情:
#define ACE_NTRACE 0 #include "ace/streams.h" #include "ace/Log_Msg.h" #include "ace/Log_Msg_Callback.h" #include "ace/Log_Record.h" #include "ace/SString.h" #include "ace/OS.h" #include "ace/config-macros.h" #define ACE_static_cast(TYPE, EXPR) static_cast<TYPE> (EXPR) #ifdef _DEBUG #pragma comment(lib,"ACEd.lib") #else #pragma comment(lib,"ACE.lib") #endif class CallBack : public ACE_Log_Msg_Callback { public: void log(ACE_Log_Record &log_recode) { cerr << "Log Message Received: " << endl; unsigned long msg_severity = log_recode.type(); ACE_Log_Priority prio = ACE_static_cast(ACE_Log_Priority,msg_severity); const ACE_TCHAR * prio_name = ACE_Log_Record::priority_name(prio); cerr << "\tType: " << ACE_TEXT_ALWAYS_CHAR(prio_name) << endl; cerr << "\tLength: " << log_recode.length() << endl; const time_t epoch = log_recode.time_stamp().sec(); cerr << "\tTime_stamp " << ACE_TEXT_ALWAYS_CHAR(ACE_OS::ctime(&epoch)) << flush; cerr << "\tPid: " << log_recode.pid() << endl; ACE_CString data(">>"); data += ACE_TEXT_ALWAYS_CHAR(log_recode.msg_data()); cerr << "\tMsgData: " << data.c_str() << endl; } }; int ACE_TMAIN (int,ACE_TCHAR*[]) { CallBack *callback = new CallBack; ACE_LOG_MSG->set_flags(ACE_Log_Msg::MSG_CALLBACK); ACE_LOG_MSG->clr_flags(ACE_Log_Msg::STDERR); ACE_LOG_MSG->msg_callback(callback); ACE_TRACE(ACE_TEXT("main")); ACE_DEBUG((LM_DEBUG,ACE_TEXT("%IHi Mom\n"))); ACE_DEBUG((LM_DEBUG,ACE_TEXT("%IGoodnight\n"))); getchar(); return 0; }
運行結果:
從上面的例子我們可以看出,我們對ACE_Log_Record的內部擁有相當的訪問權。我們不僅能夠改變消息文本,事實上,我們還能改變我們想要改變的任何值。
ACE_Log_Record的各個屬性極其含義:
type 日志記錄的類型
priority type的同義詞
priority_name 日志記錄的優先級的名稱
length 日志記錄的長度,由日志記錄的創建這設置
time_stamp 日志記錄的時間戳-通常是創建時間:由記錄的創建者設置
pid 創建該日志記錄實例的進程ID
msg_data 日志記錄的文本消息
msg_data_len msg_data屬性的長度
ACE Logging Service是一種可配置的兩層服務,可用於替換UNIX syslog。
syslog和Windows Event Logger都能把他們的工作完成的相當好,甚至還能用於捕捉來自遠地主機的消息。但如果你擁有的事混合環境,他們就不行了
ACE netsvcs日志框架具有客戶/服務器設計。在網絡中的某個主機上運行日志服務器,它將接收其他任何主機發出的日志請求。在網絡中的其他每一個需要使用分布式日志記錄器的主機上,你都要調用日志客戶。日志客戶的作用有點像代理,它接收本地系統上的客戶端發出的日志請求,把他們轉發給服務器。這種設計似乎有點奇怪,但它有助於防止大量客戶連接沖擊服務器,而這些連接可能有很多都是瞬時的。通過使用代理器,每個主機上的代理都會吸收一點沖擊,這樣大家的處境都能更好些
配置客戶端和客戶代理,我們要使用ACE Service Configurator框架
要啟動服務器,首先要創建一個名為 server.conf的文件
dynamic Logger Service_Object *ACE: _make_ACE_Logging_Strategy() "-s foobar -f STDERR|OSTREAM|VERBOSE" dynamic Server_Logging_Service Service_Object *netsvcs: _make_ACE_Server_Logging_Acceptor() active "-p 20009"
該文件只有兩行,每行以dynamic開頭。
第一行定義了日志記錄策略,把日志輸出寫往標准錯誤以及名為foobar的文件關聯在一起的輸出流。這一行還要求輸出詳細的日志消息,而不是使用更為簡潔的格式
第二行讓服務器在計算機上可用的所有網絡接口上偵聽客戶連接,使用TCP端口20009。
然后可以這樣啟動服務器:
$ACE_ROOT/netsvcs/servers/main -f server.conf
然后是客戶代理的創建配置和啟動。為客戶代理創建一個名為client.conf的配置文件:
dynamic Client_Logging_Service Service_Object * netsvcs:_make_ACE_Client_Logging_Accepotr() active "-p 20009 -h localhost"
該文件一行,告訴代理,服務器會在20009端口偵聽,-h localhost 設置日志服務器所在主機名
我們只提供了服務器的偵聽端口,並沒有為代理的客戶提供端口值。這個值稱為日志記錄器鍵(logger key),取決於客戶端日志記錄器的構建平台功能,其形式和值會發生變化
在有些平台上,它是一個管道:在管道不可用時,它是位於localhost:20012的回送(loopback)TCP socket。如果想讓你的客戶代理使用本同的偵聽地址,你可以在client.conf中用-k 參數進行指定
啟動客戶代理如下:
$ACE_ROOT/netsvcs/servers/main -f client.conf
使用日志服務器也不是很復雜:
#include "ace/Log_Msg.h" #ifdef _DEBUG #pragma comment(lib,"ACEd.lib") #else #pragma comment(lib,"ACE.lib") #endif int ACE_TMAIN(int,ACE_TCHAR *argv[]) { ACE_LOG_MSG->open(argv[0],ACE_Log_Msg::LOGGER,ACE_DEFAULT_LOGGER_KEY); ACE_TRACE(ACE_TEXT("main")); ACE_DEBUG((LM_DEBUG,ACE_TEXT("%IHi Mom\n"))); ACE_DEBUG((LM_INFO,ACE_TEXT("%IGoodnight\n"))); return 0; }
與syslog例子的情況一樣,當我們想要使用日志服務時,必須使用open()方法;
只使用set_flags()是不夠的。還要注意open()的參數ACE_DEFAULT_LOGGER_KEY。它必須與客戶日志記錄器所用的日志記錄器鍵相同:
如果你在client.conf中庸-k選項改變了它,你必須向open()指定所用的新值。
總而言之,在每個你想要使用日志服務的機器上,必須運行客戶日志記錄器的一個實例。每個實例都要進行配置,連接到在你的網絡上的日志服務器的一個實例。
當然,你要在適當的系統上運行這個服務器實例
你的應用可以直接與日志服務器實例通信。這種做法有兩個問題:
1,由於增加了連接和日志記錄邏輯,你的程序現在會更復雜
2,你又壓垮服務器實例的危險,因為你移除了客戶代理提供的伸縮能力
如果仍想要與日志服務器實例直接交談,這里有一種方法:
#include "ace/streams.h" #include "ace/Log_Msg.h" #include "ace/Log_Msg_Callback.h" #include "ace/Log_Record.h" #include "ace/SOCK_Stream.h" #include "ace/SOCK_Connector.h" #include "ace/INET_Addr.h" #define LOGGER_PORT 20009 #ifdef _DEBUG #pragma comment(lib,"ACEd.lib") #else #pragma comment(lib,"ACE.lib") #endif class Callback : public ACE_Log_Msg_Callback { public: Callback() { this->logger_ = new ACE_SOCK_Stream; ACE_SOCK_Connector connector; ACE_INET_Addr addr(LOGGER_PORT,ACE_DEFAULT_SERVER_HOST); if(connector.connect(*(this->logger_),addr) == -1) { delete this->logger_; this->logger_ = 0; } } virtual ~Callback() { if(this->logger_) { this->logger_->close(); } delete this->logger_; } void log(ACE_Log_Record &log_record) { if(!this->logger_) { log_record.print(ACE_TEXT(""),ACE_Log_Msg::VERBOSE,cerr); return; } size_t len = log_record.length(); log_record.encode(); if(this->logger_->send_n)((char*)&log_record,len) == -1) { delete this->logger_; this->logger_ = 0; } } private: ACE_SOCK_Stream *logger_; }; int ACE_TMAIN(int,ACE_TCHAR *argv[]) { Callback *callback = net Callback; ACE_LOG_MSG->set_flags(ACE_Log_Msg::MSG_CALLBACK); ACE_LOG_MSG->clr_flags(ACE_Log_Msg::STDERR); ACE_LOG_MSG->msg_callback(callback); ACE_TRACE(ACE_TEXT("main")); ACE_DEBUG((LM_DEBUG,ACE_TEXT("%IHi Mom\n"))); ACE_DEBUG((LM_INFO,ACE_TEXT("%IGoodnight\n"))); return 0; }
這看起來與我們之前的回調例子很像。我們使用回調掛鈎,捕捉含有我們信息的ACE_Log_Record實例。然后我們的新CallBack對象會把消息發往日志服務器
上面代碼重點是,回調對象的構造器會打開一個連接到日志服務的socket。隨后,log()方法通過這個socket把ACE_Log_Record實例發往服務器。因為ACE_Log_Record有若干屬性是數字的,為了確保他們的格式是網絡中立的,我們必須在發送它們之前使用encode()方法。如果運行你的應用的主機的字節序與運行你的日志服務器的主機不同,這樣能防止出現大混亂。