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是通过定义全局函数指针变量来指向用户定义的日志回调函数。
static event_log_cb log_fn = NULL;
void event_set_log_callback(event_log_cb cb)
{
log_fn = cb;
}
若用户不自己定义日志回调函数,libevent会调用默认的日志函数event_log函数,只是简单地根据参数判断日志记录的级别,然后把级别和内容输出。
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);//输出到标准错误终端上
}
}
当函数指针log_fn不用NULL时,就调用log_fn指向的函数。否则就直接向stderr输出日志信息。
Libevent的日志函数如下:
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, ...);
这些函数,函数实现大都差不多,下面是其中两个实现
void event_sock_warn(evutil_socket_t sock, const char *fmt, ...)
{
va_list ap;
int err = evutil_socket_geterror(sock);
va_start(ap, fmt);
event_logv_(EVENT_LOG_WARN, evutil_socket_error_to_string(err), fmt, ap);
va_end(ap);
}
void event_errx(int eval, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
event_logv_(EVENT_LOG_ERR, NULL, fmt, ap);
va_end(ap);
event_exit(eval);
}
主要是设置调用的日记级别,把可变参数用va_list变量记录,然后调用event_logv_()函数,下面是event_logv_()函数实现:
void event_logv_(int severity, const char *errstr, const char *fmt, va_list ap)
{
char buf[1024];
size_t len;
if (severity == EVENT_LOG_DEBUG && !event_debug_get_logging_mask_())
return;
//如果有可变参数,就把可变参数格式化到一个缓存区buf中。
if (fmt != NULL)
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);
}
}
//把缓存区的数据作为一条日志记录,调用Libevent的日志函数
event_log(severity, buf);//这个函数是最前面的说到的日志处理函数
}
最后是日志函数的声明,在log-internal.h里,如同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)
EVENT2_EXPORT_SYMBOL
void event_err(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3) EV_NORETURN;
EVENT2_EXPORT_SYMBOL
void event_warn(const char *fmt, ...) EV_CHECK_FMT(1,2);
EVENT2_EXPORT_SYMBOL
void event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...) EV_CHECK_FMT(3,4)
EV_NORETURN;
EVENT2_EXPORT_SYMBOL
void event_sock_warn(evutil_socket_t sock, const char *fmt, ...) EV_CHECK_FMT(2,3);
EVENT2_EXPORT_SYMBOL
void event_errx(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3) EV_NORETURN;
EVENT2_EXPORT_SYMBOL
void event_warnx(const char *fmt, ...) EV_CHECK_FMT(1,2);
EVENT2_EXPORT_SYMBOL
void event_msgx(const char *fmt, ...) EV_CHECK_FMT(1,2);
EVENT2_EXPORT_SYMBOL
void event_debugx_(const char *fmt, ...) EV_CHECK_FMT(1,2);
attribute format属性可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。format属性告诉编译器,按照printf,
scanf等标准C函数参数格式规则对该函数的参数进行检查。这在我们自己封装调试信息的接口时非常的有用。
format的语法格式为:
format (archetype, string-index, first-to-check)
其中,“archetype”指定是哪种风格;“string-index”指定传入函数的第几个参数是格式化字符串;“first-to-check”指定从函数的第几个参数开始按上述规则进行检查。
具体的使用如下所示:
__attribute__((format(printf, a, b)))
__attribute__((format(scanf, a, b)))
举个例子:
void event_sock_err(int eval, evutil_socket_t sock, const char
*fmt, ...) EV_CHECK_FMT(3,4) EV_NORETURN;
在event_sock_err里,char *fmt是第三个参数,”…”第四个参数开始检查,EV_CHECK_FMT(3,4)。
参考:https://blog.csdn.net/luotuo44/article/details/38317797