Libevent 如何定义自己的日志输出格式
Libevent 的日志系统中定义了四个日志的输出等级,默认情况下会将日志信息输出到终端。不过 Libevent 允许用户设置自定义的日志输出回调函数以及异常日志输出函数,以提供日志信息的进一步解释和输出重定向。回调函数的指针定义如下:
1 | //event.h |
Libevent 默认将这两个回调函数置为 NULL,用户可以通过函数event_set_log_callback和event_set_fatal_callback进行设置
1 | //log.c |
若用户没有自定义日志输出函数,那么 Libevent 会根据 severity 的值调用相应的默认日志输出函数,并向 stderr 输出日志信息。具体的实现如下:
1 | //log.c |
日志系统的底层函数
看完了 Libevent 的日志系统留给用户的两个接口定义后,我们可以来看看 Libevent 默认的日志输出函数。在源码当中,日志系统相关的文件主要有两个: log.c 和 log-internal.h。我们先来看 log-internal.h 文件
1 | // log-internal.h |
在 log-internal.h 中,我们可以看到 Libevent 中的日志系统其实运用到了编译器属性 attribute 的一些技巧。
attribute 是 GNU C 下的一种机制,可以用来设置函数属性、变量属性以及类型属性,它的语法格式如下
1 | __attribute__((attribute-list)) |
其中 attribute-list 是用逗号分隔开的列表。在 Libevent 的日志系统中,主要应用到的有以下三种 attribute 属性:
1. attribute((format(archetype, string-index, first-to-check)))
该属性可以为函数添加上类似于 print、scanf、strftime 以及 strfmon 风格的格式化字符串,从而可以帮助我们完成对格式化字符串的类型检查。其中,archetype 代表参数的格式化风格,string-index 代表格式化字符串位于形参列表中的哪个位置,first-to-check 代表可变参数列表在形参列表中的哪个位置。结合一个 event_warn 的例子:
void event_warn(const char *fmt, …) attribute((format(printf, 1, 2)));
attribute((format(printf, 1, 2))) 代表按照 printf 的风格检查格式化字符串,其中参数列表的第 1 个参数代表格式化字符串,第 2 个参数代表可变参数列表。
2. attribute((noreturn))
noreturn 是一个函数属性,用来通知编译器该函数在执行后不会将控制权返回给它的调用者。以函数 event_err为例,当程序使用该日志函数时,说明程序已然产生了严重的错误,不能再继续执行下去,除了抛出异常,或者调用如exit()或abort()函数终止程序以外,可以利用 noreturn 来处理。当函数执行 event_err 时,主程序会将控制权交给 event_err 函数,当 event_err 执行完毕后,控制流将不会返回主程序,而是停留在此处。
3. attribute ((visibility(“default”)))
visibility属性可以将从动态库中导出可见符号,default 代表可见,而 hidden 代表隐藏。新版本的 Linux 内核限制了动态库的符号可见性,因为这样做可以提高程序的模块性,同时动态库装载和识别的符号越少,程序启动和运行的速度也就越快。Libevent 中被 EVENT2_EXPORT_SYMBOL 修饰的函数符号都将会被导出,因此使用该动态库程序都可以使用到这些函数。
对于event_err,event_sock_err,event_debugx_等函数,它们的实现大同小异,区别仅是部分参数的不同,以下选取部分函数进行讨论
1 | void event_err(int eval, const char *fmt, ...) |
上述的日志函数大体的结构是相同的,都采用了可变参数列表的形式实现,最终通过event_logv_以及event_log两个函数将日志输出。
一些语法糖
在 log-internal.h 当中还定义了一个 event_debug 的宏,可以用于在调试期间打印出调试信息。利用这个宏来代替event_debugx_函数,一方面可以使得调试语义更加清晰,另一方面也可以非常方便地实现调试模式的开关。它的定义如下:
1 |
总结
- Libevent 的日志系统为用户提供了两个接口
event_log_cb和event_fatal_cb,以便用户实现自定义的日志处理功能,在需要的时候可以使用event_set_log_callback和event_set_fatal_callback进行设置 - 如果用户没有定义自己的日志处理回调函数,则采用 Libevent 默认的日志处理系统,它将日志输出等级定义为四个,并会将日志输出到终端。
- 在日志系统的开发过程当中,利用好 GNU C 的一些机制,如 attribute 等,可以极大简化一些繁琐的检查过程,不仅有利于调试,帮助找到一些难以发现的错误,还有助于编译器进行优化。
- 本文作者: Phoenix
- 本文链接: https://phoenix500526.github.io/2020/08/11/Libevent-源码阅读笔记-三-日志系统的实现/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!