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()方法。如果运行你的应用的主机的字节序与运行你的日志服务器的主机不同,这样能防止出现大混乱。