写在前面:
这个源码是分析libevent-2.0.20-stable, 并非最新版本的libevent,作者并没有全看源码,在这里会推荐以下参考的一些网站,也欢迎大家在不足的地方提出来进行讨论。
在event2/event.h中定义了错误类型和错误的回调函数
#define EVENT_LOG_DEBUG 0 #define EVENT_LOG_MSG 1 #define EVENT_LOG_WARN 2 #define EVENT_LOG_ERR 3 #define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG #define _EVENT_LOG_MSG EVENT_LOG_MSG #define _EVENT_LOG_WARN EVENT_LOG_WARN #define _EVENT_LOG_ERR EVENT_LOG_ERR // 日志回调函数的格式和日志定制函数 typedef void (*event_log_cb)(int severity, const char *msg); void event_set_log_callback(event_log_cb cb); // 出错回调函数的格式和错误定制函数 typedef void (*event_fatal_cb)(int err); void event_set_fatal_callback(event_fatal_cb cb);
在实现上,在log.c中,定义了两个全局的静态变量和设置两个静态变量的回调函数
static event_fatal_cb fatal_fn = NULL; void event_set_fatal_callback(event_fatal_cb cb) { fatal_fn = cb; } static event_log_cb log_fn = NULL; void event_set_log_callback(event_log_cb cb) { log_fn = cb; }
从event_set_log_callback的实现代码可以看到,并没有对这个参数cb做任何检查。
Libevent的默认日志处理函数event_log函数和错误处理函数还是很简陋的,前者只是简单判断错误类型,后者只是简单地根据参数判断日志记录的级别,然后把级别和日志内容输出。
static void event_exit(int errcode) { if (fatal_fn) { //如果错误出错函数被定义 fatal_fn(errcode); //执行用户的出错函数,但是并不能让服务器不退出! exit(errcode); /* should never be reached */ } else if (errcode == _EVENT_ERR_ABORT) abort(); else exit(errcode); } static void event_log(int severity, const char *msg) { if (log_fn) //定义了自己的出错处理函数 log_fn(severity, msg); //调用自己的出错函数 else { const char *severity_str; switch (severity) { case _EVENT_LOG_DEBUG: severity_str = "debug"; break; case _EVENT_LOG_MSG: severity_str = "msg"; break; case _EVENT_LOG_WARN: severity_str = "warn"; break; case _EVENT_LOG_ERR: severity_str = "err"; break; default: severity_str = "???"; break; } (void)fprintf(stderr, "[%s] %s\n", severity_str, msg); // 仅仅把错误输出到stderr // fixme: 可以加上时间、文件名、行号和函数名 } }
日志API以及日志消息处理流程:
在将日志处理函数API之前,我们需要先了解两个宏的用法
#define EV_CHECK_FMT(a,b) __attribute__((format(printf, a, b))) #define EV_NORETURN __attribute__((noreturn))
第一个宏,口说无凭,我们先做个小小test,来看看这个宏的作用
// 仿照日志处理api的方法定义了一个 extern void myprint(const char *format,...) __attribute__((format(printf,1,2))); void myprint(const char* format, ...) { va_list ap; va_start(ap, format); char buf[1024]; memset(buf, 0 , sizeof(buf)); vsnprintf(buf, 1023, format, ap); (void)printf("%s",buf); va_end(ap); } int main() { myprint("i=%d/n",6); myprint("i=%s/n",6); myprint("i=%s/n","abc"); myprint("%s,%d,%d/n",1,2); }
编译结果
warning: format ‘%s’ expects argument of type ‘char*’, but argument 2 has type ‘int’ [-Wformat=] myprint("i=%s/n",6); ^ warning: format ‘%s’ expects argument of type ‘char*’, but argument 2 has type ‘int’ [-Wformat=] myprint("%s,%d,%d/n",1,2); ^ warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=]
format属性可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。
format (archetype, string-index, first-to-check) 第一参数需要传递“archetype”指定是哪种风格,这里是 NSString;“string-index”指定传入函数的第几个参数是格式化字符串;“first-to-check”指定第一个可变参数所在的索引.
//void __attribute__((noreturn)) onExit(); void onExit() {} int test(int state) { if (state == 1) { onExit(); }else { return 0; } } int main() { test(1); }
当注释取消的时候,编译时候,会出现一条警告‘noreturn’ function does return。
作用:定义有返回值的函数时,而实际情况有可能没有返回值,此时编译器会报错。
在log-internal.h中定义了一系列日志处理函数API
void event_err(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3) EV_NORETURN; void event_warn(const char *fmt, ...) EV_CHECK_FMT(1,2); void event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...) EV_CHECK_FMT(3,4) EV_NORETURN; void event_sock_warn(evutil_socket_t sock, const char *fmt, ...) EV_CHECK_FMT(2,3); void event_errx(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3) EV_NORETURN; void event_warnx(const char *fmt, ...) EV_CHECK_FMT(1,2); void event_msgx(const char *fmt, ...) EV_CHECK_FMT(1,2); void _event_debugx(const char *fmt, ...) EV_CHECK_FMT(1,2);
这些函数实现的功能大致为,获取错误的名称,获取错误描述,获取输出格式,获取输出字符串。
然后它们会共同调用_warn_helpr这个函数
/** * 仅供内部函数调用 */ static void _warn_helper(int severity, const char *errstr, const char *fmt, va_list ap) { char buf[1024]; size_t len; if (fmt != NULL) // evutil_vsnprintf可以vsnprntf,跨平台设计本文不涉及,细节它会在buf[sizeof(buf) -1]处替换成/0 evutil_vsnprintf(buf, sizeof(buf), fmt, ap); else buf[0] = '\0'; if (errstr) { len = strlen(buf); if (len < sizeof(buf) - 3) { evutil_snprintf(buf + len, sizeof(buf) - len, ": %s", errstr); } } event_log(severity, buf); }
小结:libevent的日志库较为简略,用了两个全局的回调函数指针实现自定义处理,几乎没有做什么特别处理,不过在检查输入格式和输入信息的匹配值得学习。
个人博客链接 https://justsolitude.com/2018/09/11/libevnt%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%901/