本文隶属于专题系列: Libevent源码分析

日志处理:

        在Libevent的源码中,经常会见到形如event_warn、event_msgx、event_err之类的函数。这通常出现在代码中一些值是不合理时。这些函数就是Libevent的日志函数。它能把这些不合理的情况打印出来,告知用户。

定制日志回调函数:

        Libevent在默认情况下,会将这些日志信息输出到终端上。这当然就不利于日后的观察。为此,Libevent允许用户定制自己的日志回调函数。所有的日志函数在最后输出信息时,都会调用日志回调函数的。所以用户可以通过定制自己的日志回调函数(在回调函数中把信息输出到一个文件上),方便日后的查看。定制回调函数就像设置自己信号处理函数那样,设置一个日志回调函数。当有日志时,Libevent库就会调用这个日志回调函数。

        回调函数的格式和日志定制函数如下所示:

typedef void (*event_log_cb)(int severity, const char *msg);
void event_set_log_callback(event_log_cb cb);

        回调函数中的第一个参数severity是日志级别类型,有下面这些:

#define EVENT_LOG_DEBUG 0
#define EVENT_LOG_MSG   1
#define EVENT_LOG_WARN  2
#define EVENT_LOG_ERR   3
/* Obsolete names: these are deprecated, but older programs might use them.
 * They violate the reserved-identifier namespace. */
#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

        值得注意的是,不能在你的日志回调函数里面调用任何Libevent提供的API函数,否则将发生未定义行为。

        在实现上,Libevent是通过定义一个全局函数指针变量来保存用户在日志定制函数中传入的参数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函数还是很简陋的,只是简单地根据参数判断日志记录的级别,然后把级别和日志内容输出。复杂一点的日志功能,可以参考muduo日志功能。当然也有log4pp、log4xx这些把一个重量级的日志库。

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);//输出到标准错误终端上
	}
}
        从event_log函数中可以看到,当函数指针log_fn不用NULL时,就调用log_fn指向的函数。否则就直接向stderr输出日志信息。所以,设置自己的日志回调函数后,如果想恢复Libevent默认的日志回调函数,只需再次调用event_set_log_callback函数,参数设置为NULL即可。

日志API以及日志消息处理流程:

        Libevent的日志API的使用也是挺简单的。首先,使用者确定要记录的日志的级别和错误类型,然后调用对应的日志函数。有下面这些可供选择的日志函数。

void event_err(int eval, const char *fmt, ...);
void event_warn(const char *fmt, ...);
void event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...);
void event_sock_warn(evutil_socket_t sock, const char *fmt, ...);
void event_errx(int eval, const char *fmt, ...);
void event_warnx(const char *fmt, ...);
void event_msgx(const char *fmt, ...);
void _event_debugx(const char *fmt, ...);
        这些函数都是声明在log-internal.h文件中。所以用户并不能使用之,这些函数都是Libevent内部使用的。

        这些函数内部实现都差不多。下面是其中几个实现

void
event_warn(const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	_warn_helper(_EVENT_LOG_WARN, strerror(errno), fmt, ap);
	va_end(ap);
}
void
event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...)
{
	va_list ap;
	int err = evutil_socket_geterror(sock);
	va_start(ap, fmt);
	_warn_helper(_EVENT_LOG_ERR, evutil_socket_error_to_string(err), fmt, ap);
	va_end(ap);
	event_exit(eval);
}
        可以看到,主要是设置调用的日记级别,把可变参数用va_list变量记录,然后调用_warn_helper这个辅助函数。其中,event_warn和event_warnx的区别是_warn_helper函数的第二个参数分别是strerror(errno)和NULL。

        而_warn_helper这个辅助函数也是蛮简单的。

static void
_warn_helper(int severity, const char *errstr, const char *fmt, va_list ap)
{
	char buf[1024];
	size_t len;
	//如果有可变参数,就把可变参数格式化到一个缓存区buf中。
	if (fmt != NULL)
		evutil_vsnprintf(buf, sizeof(buf), fmt, ap);
	else
		buf[0] = '\0';
	//如果有额外的信息描述,把这些信息追加到可变参数的后面。
	if (errstr) {
		len = strlen(buf);
	   //-3是因为还有另外三个字符,冒号、空格和\0。
		if (len < sizeof(buf) - 3) {
			evutil_snprintf(buf + len, sizeof(buf) - len, ": %s", errstr);
		}
	}
	//把缓存区的数据作为一条日志记录,调用Libevent的日志函数。
	event_log(severity, buf);//这个函数也是最前面的说到的那个日志处理函数
}
        这些日志函数的使用,如同printf函数那样,支持%s, %d之类的格式化输出。比如:
event_warnx("Far too many %s (%d)", "wombats", 99);
        值得注意的是,也如同printf那样,可变参数中的参数要和第一个参数中的格式要求相匹配。在这些日志函数的声明中,如果是GNU 的编译器,将会检查是否匹配。其声明如下:
#ifdef __GNUC__
#define EV_CHECK_FMT(a,b) __attribute__((format(printf, a, b)))
#define EV_NORETURN __attribute__((noreturn))
#else
#define EV_CHECK_FMT(a,b)
#define EV_NORETURN
#endif
#define _EVENT_ERR_ABORT ((int)0xdeaddead)
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);

错误处理:

        Libevent库运行的时候有可能会致命发生错误,此时,Libevent的默认行为是终止程序。同日志处理一样,用户也是可以用Libevent定制自己的错误处理函数。错误处理函数的格式和定制函数如下:

typedef void (*event_fatal_cb)(int err);
void event_set_fatal_callback(event_fatal_cb cb);
        定制函数的内部实现原理和前面的日志处理函数一样,都是通过一个全局函数指针变量存储用户的错误处理函数。

        在默认情况下,Libevent处理这些致命错误时会粗暴地杀死程序,但大多数情况下,Libevent在调用这个致命处理函数前都会调用前面的日志记录函数,其级别是_EVENT_LOG_ERR。此时,虽然程序突然死了,但还是可以在日志中找到一些信息。用户的错误处理函数也应该杀死程序。

        如果要定制自己的日志处理函数和错误处理函数,那么应该在程序的一开始位置就进行定制。

你可能感兴趣的内容
0条评论

dexcoder

这家伙太懒了 <( ̄ ﹌  ̄)>
Owner