在使用 acl 库编写应用过程中,记录日志是一个非常重要的过程,acl 从几个层面提供了日志的不同记录方式。在 acl 的 C 库部分(lib_acl.a),有三个源文件与日志记录相关:acl_msg.c/acl_msg.h, acl_mylog.c/acl_mylog.h, acl_debug.c/acl_debug.h。其中,acl_mylog.c 是真正记录日志的源文件,acl_msg.c 则是在 acl_mylog.c 基础之上的二次封装,acl_debug.c 是在 acl_msg.c 基础之上的再次封装。下面根据此三个日志源文件从三个层次描述日志记录的过程。
一、ac_mylog.c/acl_mylog.h
打开 acl_mylog.h 头文件,可以看到主要有三个函数:acl_open_log(打开日志文件),acl_write_to_log(写日志)以及acl_close_log(关闭日志)—(这三个函数是最基础的日志记录过程,当然我们不必直接使用)。该库支持两类日志记录方式:1、本地文件记录方式,2、与 syslog-ng 结合的网络日志记录方式。本地文件记录方式是 acl 日志库对外提供的最简单的日志记录方式,此方式不依赖于第三方日志库,但不应用在生产环境中,因为该方式不支持日志回滚等高级特性,为了便于生产上使用,所以产生了第二种方式(与 syslog-ng 结合),查看日志打开接口(如下):
1 2 3 4 5 6 7 8 9 10 11
|
ACL_API int acl_open_log(const char *recipients, const char *plog_pre);
|
从上面的函数声明可以看出,acl 的日志记录允许同时输出至多个日志管道中(最简单的方式就是直接写入本地磁盘文件:/tmp/test.log),同时更应看到,其中有三个奇怪的日志文件表达方式:UDP:IP:PORT, TCP:IP:PORT, UNIX:/xxx,其实这三种方式均是与 syslog-ng 相关,即分别表示:
- 1、以 UDP 方式发送日志至 syslog-ng;
- 2、以 TCP 方式发送日志至 syslog-ng;
- 3、以 UNIX 域套接字方式发送日志至 syslog-ng。
因为日志管理是一个非常复杂的过程,所以在 acl 除了提供最简单的日志文件记录外,更建议用户将日志输出至 syslog-ng 中(作者自己的项目也往往是这样做的)。
二、acl_msg.c/acl_msg.h
该日志库提供了更为高级的日志记录方法,不仅提供了灵活的日志记录函数,同时还允许用户注册自己的日志记录函数库,该日志库主要函数接口如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
|
ACL_API void acl_msg_open(const char *log_file, const char *info_pre);
ACL_API void acl_msg_close(void);
上面是日志打开与关闭的函数,看上去算是相对简单。下面是几个日志记录的函数接口:
#ifdef WIN32 ACL_API void acl_msg_info(const char *fmt,...); #else ACL_API void __attribute__((format(printf,1,2))) acl_msg_info(const char *fmt,...); #endif
#ifdef WIN32 ACL_API void acl_msg_warn(const char *fmt,...); #else ACL_API void __attribute__((format(printf,1,2))) acl_msg_warn(const char *fmt,...); #endif
#ifdef WIN32 ACL_API void acl_msg_error(const char *fmt,...); #else ACL_API void __attribute__((format(printf,1,2))) acl_msg_error(const char *fmt,...); #endif
#ifdef WIN32 ACL_API void acl_msg_fatal(const char *fmt,...); #else ACL_API void __attribute__((format(printf,1,2))) acl_msg_fatal(const char *fmt,...); #endif
#ifdef WIN32 ACL_API void acl_msg_panic(const char *fmt,...); #else ACL_API void __attribute__((format(printf,1,2))) acl_msg_panic(const char *fmt,...); #endif
|
可以看到,这些函数的使用方式与 printf 类似,另外,在 UNIX 下使用 GCC 编译时前面还有一个修饰符:attribute((format(printf,m,n))),这主要是方便 gcc 编译器针对变参进行语法检查(大家应该知道变参是如此方便灵活而又如此容易出错)。
为了方便程序开发过程中的调试,下面的函数当用户未调用 acl_msg_open 打开日志而直接使用 acl_msg_xxx 写日志时,决定是否将日志信息输出至屏幕(这个函数应该在程序初始化时调用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
ACL_API void acl_msg_stdout_enable(int onoff);
前面曾说过,acl 的日志库还允许用户使用自己的日志记录过程,但要求用户必须在程序初始化时注册自己的日志处理函数,如下:
ACL_API void acl_msg_register(ACL_MSG_OPEN_FN open_fn, ACL_MSG_CLOSE_FN close_fn, ACL_MSG_WRITE_FN write_fn, void *ctx);
|
调用此函数后,以后的日志记录过程(即当用户调用:acl_msg_xxx 相关过程时)的内容便输出便由用户的日志库控制。
除了以上主要的日志函数接口,在 acl_msg 中还提供了以下几个函数,便于用户知晓程序出错原因:
1 2 3 4 5 6 7 8 9 10 11 12
|
ACL_API const char *acl_last_serror(void);
ACL_API int acl_last_error(void);
|
三、acl_debug.c/acl_debug.h
该日志函数库是在 acl_msg 之上的再一次封装,该库的思想来源于 squid 的日志记录方式,可以将日志分成不同的类别,每一个类别又分成不同的级别,这样用户就可以非常方便地通过配置文件来记录不同类别的不同级别的日志信息了。在程序初始化时需先调用如此函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
ACL_API void acl_debug_init(const char *pStr);
ACL_API void acl_debug_init2(const char *pStr, int max_debug_level);
|
其中,第一个参数是一个由日志记录类别与级别组成的字符串,格式为:类别1:最大记录级别, 类别2:最大记录级别, …。例如:100:2; 102:3; 103:4,其含义是日志将会记录类别为 100 的所有级别值小于2、类别为 101 的所有级别值小于 3 以及类别为 103 的所有级别值小于 4 的日志信息。关于记录类别需要注意:类别值最好是 >= 100,且 < 1000(当使用 acl_debug_init2 初始化时只要类别值 >= 100 即可,因为第二个参数指定了最大类别值),这是因为 acl 库内部一些保留的类别值都在 0 – 100 之间。
那么具体的使用这些类别与级别记录日志的接口是什么呢?如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
#define acl_debug(SECTION, LEVEL) \ !acl_do_debug((SECTION), (LEVEL)) ? (void) 0 : acl_msg_info
看到了吧,用户其实只需要调用一个宏即可,如下面的例子:
const char *str = "101:2; 103:4; 105:3"; acl_debug_init(str);
...... acl_debug(101, 2)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
acl_debug(105, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
acl_debug(103, 5)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
acl_debug(102, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
|
此外,为了方便,还可以传给 acl_debug_init 的参数写为:”all:1”,意思是所有类别的级别值 <= 1 的日志都将被记录,如下面的内容都会被记录:
1 2 3 4 5 6 7 8 9 10
| acl_debug_init("all:1"); ...... acl_debug(100, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL)); acl_debug(101, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL)); acl_debug(101, 0)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL)); acl_debug(102, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL)); acl_debug(103, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL)); acl_debug(104, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL)); acl_debug(105, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL)); ......
|
ok,有关日志 acl 日志记录函数就先写这些,使用者可以根据项目需要采用不同的日志记录方式。
github 地址:https://github.com/acl-dev/acl
gitee 地址:https://gitee.com/acl-dev/acl