zlog日志库(笔记) —— 配置文件

配置文件

zlog是一个通用日志库,基本功能跟专用日志库并没有本质区别。通用日志库,通过配置文件,来简化一些列日志使用和配置,以适应各种不同项目需求。而配置文件承载了这角色,为不同项目需求定制不同的功能,主要包括:把日志打印到哪儿去,用什么格式,如何转档等。

例如,一个完整的配置文件内容如下:

# comments
[global]
strict init = true
buffer min = 1024
buffer max = 2MB
rotate lock file = /tmp/zlog.lock
default format = "%d.%us %-6V (%c:%F:%L) - %m%n"
file perms = 600

[levels]
TRACE = 10
CRIT = 130, LOG_CRIT

[formats]
simple = "%m%n"
normal = "%d %m%n"

[rules]
default.*               >stdout; simple
*.*                    "%12.2E(HOME)/log/%c.log", 1MB*12; simple
my_.INFO              >stderr;
my_cat.!ERROR         "/var/log/aa.log"
my_dog.=DEBUG         >syslog, LOG_LOCAL0; simple
my_mice.*             $user_define;

单位说明:
当设置内存、文件大小,或较大数字时,可设置1k 5GB 4M这样的单位

# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 byte

注:单位大小写不敏感,1GB <=> 1gb <=> 1Gb <=> 1gB。


全局参数

可选。
全局参数以[global]开头,"[]"代表一个字节开始,四个小节顺序不能变:global -> levels -> formats -> rules。

语法格式:

(key) = (value)

比如上面的"strict init = true"中,"strict init"是key,"true"是value。

  • strict init

如果"strict init"值为true,那么zlog_init()将严格检查所有的格式和规则,任何错误都会导致zlog_init()失败并返回-1;如果为false,zlog_init()将忽略错误的格式和规则。该参数默认为true。

  • reload conf period

该选项让zlog能在一段时间后,自动重载配置文件。重载的间隔以每进程写日志的次数来定义。当写日志次数到了一定值后,内会将会调用zlog_reload()进行重载。每次zlog_reload()或zlog_init()后重新计数累加。因为zlog_reload()是原子的,重载失败继续用当前的配置信息,所以自动重载是(线程)安全的。默认值0,标识自动重载关闭。

  • buffer min和buffer max

zlog在堆上为每个线程申请缓存。"buffer min"是单个缓存最小值,zlog_init()申请这个长度的内存。写日志时,如果单条日志长度 > 缓存大小,缓存会自动扩充,直到"buffer max"。单条日志长度 > "buffer max"时,该日志就会被截断。如果"buffer max"值为0,表示不限制缓存,每次扩充为原来2倍,直到进程用完所有内存。
缓存大小默认使用byte作为单位,可以显式加上KB,MB或GB单位。"buffer min"默认值1K,"buffer max默认值2MB。

所谓截断,通常是指将末尾的几个数据替换为如"..."(zlog_buf_new()调用者决定),让用户知道内容发生阶段。

  • ratate lock file

该选项指定一个锁文件,用来保证多进程情况下日志安全转档。zlog会在zlog_init()时,以读写权限打开该文件。

请确认你执行APP的用户有权限创建和读写该锁文件。

转档日志的伪代码:

write(log_file, a_log)
if (log_file > 1M) 
    if (pthread_mutex_lock succ && fcntl_lock(lock_file) succ)
        if (log_file > 1M) rotate(log_file);
        fcntl_unlock(lock_file);
        pthread_mutex_unlock;

mutex 锁(互斥锁)用于多线程,fcntl 锁(POSIX建议锁)用于多进程。进程意外死亡后,OS会释放此进程持有的锁。这是zlog选择用fcntl锁保证安全转档的重要原因。

rotate lock file默认值是/tmp/zlog.lock。

如果rotate lock file = self,此时,zlog不会创建任何锁文件,用配置文件作为锁文件。而fnctl是建议锁(并非强制锁),用户可自由修改、存储配置文件。一般地,单个日志文件不会被不同OS用户的进程转档,所以用配置文件作为锁文件是安全的。

如果用户设置其他路径作为锁文件,如/tmp/zlog.lock,zlog会在zlog_init()时创建该文件。如果有多个OS用户的进程需要转档同一个日志文件,确认该锁文件id多个用户都可读写。

  • default format

缺省日志格式,默认值:

%d %V [%p:%F:%L] %m%n"

该格式输出形如:

2022-09-11 12:14:52 INFO [14210:test_hello.c:33] info, zlog
  • file perms

指定了创建日志文件的缺省访问权限。必须注意最后产生的日志文件权限为"file perms" & ~umask。umask是进程操作文件的权限的掩码。

默认值为0x600("rw-------"),表示只允许当前用户读写。

  • fsync period

每条规则写了一定次数的日志消息到文件后,zlog会调用fsync(3)让OS马上把数据写到硬盘。次数是每条规则单独统计的,并且在zlog_reload()后会清0。需要指出的是,在日志文件名是动态生成或者被转档的情况下,zlog不能保证把所有文件都搞定,zlog只fsync()那个时候刚刚write()的文件描述符。这提供了写日志速度和数据安全性之间的平衡。

例如:

$ time ./test_press_zlog 1 10 100000
real 0m1.806s
user 0m3.060s
sys 0m0.270s

$ wc -l press.log
1000000 press.log

$ time ./test_press_zlog 1 10 100000 # fsync period = 1K
real 0m41.995s
user 0m7.920s
sys  0m0.990s

$ time ./test_press_zlog 1 10 100000 # fsync period = 10K
real 0m6.856s
user 0m4.360s
sys 0m0.550s

如果你非常在乎安全而不是速度,建议用同步IO文件,参见【输出动作】部分。

默认值0,由OS决定何时冲刷缓存到文件。


日志等级

可选。除了支持系统默认的几个日志等级:DEBUG,INFO,NOTICE,WARN,ERROR,FATAL。zlog还支持用户自定义日志等级。语法:

(level string) = (level int), (syslog level, optional)

(level int)必须在[1, 253]之间,值越大表示越重要。

用户自定义等级

如果用户需要自定义一个等级,如TRACE(没有在默认等级中),可以按下面步骤进行配置。

步骤:
1)在配置文件中定义新的等级

$ cat $(top_builddir)/test/test_level.conf
[global]
default format  =               "%V %v %m%n"
[levels]
TRACE = 30, LOG_DEBUG
[rules]
my_cat.TRACE            >stdout;

内置默认等级(不需要写在配置文件里面):

DEBUG = 20, LOG_DEBUG
INFO = 40, LOG_INFO
NOTICEC = 60, LOG_NOTICE
WARN = 80, LOG_WARNING
ERROR = 100, LOG_ERR
FATAL = 120, LOG_ALERT
UNKNOWN = 254, LOG_ERR

自定义的等级数,必须在[1, 253]之间,其他数字非法。数字越大,代表越重要。现在, TRACE等级为30,比DEBUG(等级20)高,比INFO等级低(等级40)。这样,TRACE就能在下面的配置文件中用了。例如:

my_cat.TRACE >stdout;

意味着等级 >= TRACE的,包括INFO,NOTICE,WARN,ERROR,FATAL都会被写到标准输出中。

格式里面的转换字符%V会产生等级字符串的大写输出,%v会产生小写的等级字符串输出。

另外,在等级的定义里面,LOG_DEBUG是指当需要输出到syslog时,自定义的TRACE等级会以LOG_DEBUG输出到syslog。

2)在源代码里面直接用新的等级
注意:这个.c文件和下面的.h文件,相当于是用户程序自定义的,并非库运行时的程序。

zlog(cat, __FILE__, sizeof(__FILE__)-1, \
    __func__, sizeof(__func__)-1, __LINE__, \
    30, "test %d", 1); // 30 是刚刚自定义的TRACE

为了简单使用,创建一个.h头文件。

$ cat $(top_builder)/test/test_level.h
#ifndef __test_level_h
#define __test_level_h

#include "zlog.h"

enum {
    ZLOG_LEVEL_TRACE = 30,
    /* must equals conf file setting */
};

#define zlog_trace(cat, format, ...) \
        zlog(cat, __FILE__, sizeof(__FILE__)-1, \
        __func__, sizeof(__func__)-1, __LINE__, \
        ZLOG_LEVEL_TRACE, format, ## __VA_ARGS__)

#endif

3)这样zlog_trace就能在.c文件里面调用了

$ cat $(top_builddir)/test/test_level.c
#include <stdio.h>
#include "test_level.h"

int main(int argc, char *argv[])
{
    int rc;
    zlog_category_t *zc;
    
    rc = zlog_init("test_level.conf"); /* 以配置文件test_level.conf初始化zlog库 */
    if (rc) {
        printf("init failed\n");
        return -1;
    }

    zc = zlog_get_category("my_cat");
    if (!zc) {
        printf("get cat fail\n");
        zlog_fini(); /* 反初始化zlog库 */
        return -2;
    }

    zlog_trace(zc, "hello, zlog - trace");
    zlog_debug(zc, "hello, zlog - debug");
    zlog_info(zc, "hello, zlog - debug");
    zlog_fini();

    return 0;
}

4)最后,可以观察到输出

$ ./test_level
TRACE trace hello, zlog - trace
INFO info hello, zlog - info

这正是我们所期待的,配置文件只允许 >= TRACE等级的日志输出到屏幕上。配置文件test_level.conf中的%V %v,也显示了正确的结果。


格式(Formats)

以[formats]开始,用来定义日志的格式。语法:

(name) = "(actual formats)"

后面的规则(rules)需要引用时,就通过(name)。(name)必须由数字、字母、下划线("_")组成。(actual format)需要用双引号包裹,可以由转换字符串组成,见【转换格式串】部分。

e.g. 截取formats及rules中引用示例

[formats]
simple = "%m%n"
normal = "%d %m%n"

[rules]
default.*               >stdout; simple

这里,"simple"就是格式name,"%m%b"就是转换格式串。

转换格式串

转换格式串的设计是从C标准的printf抄过来的。一个转换格式串由文本字符和转换说明组成。

转换格式串用于规则(rules)的日志文件路径,输出格式(format)中。

你可以把任意的文本字符放到转换格式串里面。

每个转换说明都是以百分号(%)打头,后面跟可选的宽度修饰符,最后以转换字符结尾。转换字符决定了输出什么数据,例如分类名、级别、时间日期、进程号等。宽度修饰符控制了这个字段的最大最小宽度、左右对齐。

例如,如果转换格式串是

"%d(%m-%d %T) %-8V [%p:%F:%L] %m%n"

说明:"%"开头+若干字母,能被zlog识别的,称为转换字符。
像"-" ":" "[" "]" " "()等,称为文本字符。
这里%-8V,表示日志级别要大写,并且左对齐,占用8个字符宽度。

源代码中,写log语句是:

zlog_info(c, "hello, zlog");

将会输出:

09-11 15:37:21 INFO     [27608:test_hello.c:33] info, zlog

转换字符

zlog可以识别的转换字符有:

转换字符 效果 示例
%c 分类名 aa_bb
%d() 打印日志的时间。后面要跟一对小括号(),内含说明具体的日期格式。
就像%d(%F)或%d(%m-%d %T)。
%d(%F) 2022-12-01
%d(%m-%d %T) 12-01 17:17:42
%d(%T) 17:17:42.035
%d 2022-02-14 17:03:12
%d()
%E() 获取环境变量的值 %E(USER) martin
%ms 毫秒,3位数字字符串
取自gettimeofday(2)
013
%us 微秒,6位数字字符串
取自gettimeofday(2)
002323
%F 源代码文件名,来源于__FILE__宏。某些编译器,__FILE__是绝对路径,
可用%f去掉目录只保留文件名,或编译器有选项可用调节
test_hello.c
或某些编译器下,
/home/zlog/src/test/test_hello.c
%f 源代码文件名,输出%F最后一个'/'后面的部分。会有一定性能损失。 test_hello.c
%H 主机名,来源于gethostname(2) zlog-dev
%L 源代码行号,来源于__LINE__宏 125
%m 用户日志,用户从zlog接口输入的日志(内容) hello, zlog
%M MDC(mapped diagnostic context),每个线程一张键值对表,输出键相对应的值。后面必需跟一对小括号()内含键。例如,%M(clientNumber),clientNumber。可参见【MDC】 %M(clientNumber) 123456
%n 换行符,目前还不支持Windows换行符 \n
%p 进程ID,来源于个getpid() 2134
%U 调用函数名,源于__func__(C99)或__FUNCTION__(gcc) main
%V 日志级别,大写 INFO
%v 日志级别,小写 info
%t 16进制表示线程ID,来源于phtread_self()
"0x%x", (unsigned int) pthread_t
0xba01e70
%T 相当于%t,不过以长整型表示
"%lu", (unsinged long)pthread_t
140633234859776
%% 一个百分号 %
%[其他字符] 解析为错误,zlog_init()将会失败

宽度修饰符

通常,数据按原样输出。不过,因为不同消息的各字段长度不一样,如果要控制不同消息对齐,该怎么办?
可以使用宽度修饰符,控制最小字段宽度、最大字段宽度和左右对齐方式。需要付出一定性能代价。

宽度修饰符,可选,位于%和转换字符之间。

  • 左对齐标识

左对齐标识,用减号(-)表示。

  • 最小字段宽度

十进制数常量,表示最少有几个字符会被输出。如果数据本来没有那么多字符,将会填充空格(左对齐/右对齐)直到最小宽度为止。默认填充在字段的左边,也就说右对齐。如果使用了左对齐标识,那么就填充在右边。填充字符为空格()。如果数据宽度 > 最小宽度,则按数据宽度输出,不会截断数据。

例如,前面的

[formats]
simple = "%d(%m-%d %T) %-8V [%p:%F:%L] %m%n"
%-8V:表示日志级别字段左对齐,最小宽度8.

输出log:

09-11 15:37:21 INFO     [27608:test_hello.c:33] info, zlog

即这里日志级别"INFO"后会跟5个空格(8 - 3 = 5),还有1个空格是simple原有固定的空格。

而最大字段宽度,可以截断数据。最大字段宽度是放在一个句点号(.)后面的十进制数常量。如果数据宽度超过最大字段宽度,则尾部多余的字符(超过最大字段宽度的部分)将会被截去。如果最大宽度字段8,数据宽度10,那么最后2个字符会被丢弃。该行为和C的printf一样,把后面的部分截断。

下面是各种宽度修饰符和分类转换字符(%c)配合一起使用的例子。

宽度修饰符 左对齐? 最小字段宽度 最大字段宽度 附注
%20c 20 左补空格,如果分类名 < 20个字符
%-20c 20 右补空格,如果分类名 < 20个字符
%.30c 30 如果分类名 > 30个字符,取前30个字符,去掉后面的
%20.30c 20 30 如果分类名 < 20个字符,左补齐空格。如果在20~30之间,
按原样输出。如果 > 30个字符,取前30个,去掉后面的
%-20.30c 20 30 如果分类名 < 20个字符,右补齐空格。如果在20~30之间,
按原样输出。如果 > 30个字符,取前30个,去掉后面的

时间字符

转换字符d 支持的时间字符,即%d()括号中的内容。
所有字符都由strftime(2)生成。在Linux OS上支持的是:

字符 效果 例子
%a 一星期中各天的缩写名,根据locale显示 Wed
%A 一星期中各天的全民,根据locale显示 Wednesday
%b 缩写的月份名,根据locale显示 Mar
%B 月份全名,根据locale显示 March
%c 当地时间和日期的全表示,根据locale显示 Thu Feb 16 14:16:35 2020
%C 世纪(年/100),2位的数字(SU) 20
%d 一个月中的某一天(01~31) 06
%D 相当于%m/%d/%y.(美国人专用,建议少用)(SU) 02/16/22
%e 类似于%d,一个月中的某一天,但头上的0被替换位空格(SU) 6
%F 相当于%Y-%m-%d(ISO 8601日期格式)(C99) 2022-02-16
%G 采用%V定义的年,如果那年的前几天不算新年的第一周,就算上一年 2022
%g 相当于%G,不过不带世纪(00-99) 22
%h 相当于%b(SU) Feb
%H 小时,24小时表示(00~23) 14
%I (大写i)小时,12小时表示(01-12) 02
%j 一年中的各天(001-366) 047
%k 小时,24小时表示(0-23);一位的前面为空格(可和%H比较)(TZ) 15
%l (小写L)小时,12小时表示(0-12);一位的前面为空格(可和% I比较)(TZ) 3
%m 月份(01-12) 02
%M 分钟(00-59) 11
%n 换行符(SU) \n(
%p "AM"或"PM",根据当时的时间,根据locale显式相应的值,
如"上午""下午"。中午是"PM",凌晨是"AM"
PM
%P 相当于%p,不过是小写,根据locale显式相应的值(GNU) pm
%r 时间+后缀AM或PM。在POSIX locale下相当于%l:%M:%S %p.(SU) 03:11:54 PM
%R 小时(24小时制):分钟(%H:%M) (SU)如果要带秒,见%T 15:11
%s Epoch以来的秒数,即1970-01-01 00:00:00 UTC.(TZ) 1329376487
%S 秒(00-60)(允许60是为了闰秒) 54
%t 制表符tab(SU)
%T 小时(24小时制):分钟:秒(%H:%M:%S)(SU) 15:14:47
%u 一周的天序号(1-7),周一是1,另见%w(SU) 4
%U 一年中的星期序号(00-53),周日是一周的开始,
一年中第一个周日所在周d是第01周。另见%V和%W
07
%V ISO 8601星期序号(0-6),周日是0。另见%u 07
%w 一周的天序号(0-6),周日是0。另见%u 4
%W 一年中的星期序号(00-53),周一是一周的开始,
一年中第一个周一所在的周是第01周。另见%V和%W
07
%x 当前locale下的偏好日期 02/16/22
%X 当前locale下的偏好时间 15:14:47
%y 不带世纪数目的年份(00-99) 22
%Y 带世纪数目的年份 2022
%z 当前时区相对于GMT时间的偏移量。采用RFC 822-conformant来计算
(using "%a,%d %b %Y %H:%M:%S %z")(GNU)
+0800
%Z 时区名(如果有的话) CST(美国、澳大利亚、古巴
或中国的标准时间)
%% 一个百分号 %

规则(Rules)

描述了日志怎么被过滤、格式化以及被输出。语法:

(category).(level)    (output), (options, optional); (format name, optional)

当zlog_init()被调用时,所有规则都会被读到内存中。当zlog_get_category()被调用时,规则就会被分配给分类。在实际写日志的时候,例如zlog_info()被调用的时候,就会比较这个INFO和各条规则的等级,来决定这条日志会不会通这条规则输出。当zlog_reload()被调用时,配置文件会被重新读入,包括所有的规则,并且重新计算分类对应的规则。

级别匹配

规则中,level字段决定级别。
zlog有6个默认的级别:DEBUG, INFO, NOTICE, WARN, ERROR, FATAL。像其他的日志函数库,aa.DEBUG意味着任何 >= DEBUG级别的日志会被输出。

表达式 含义
* 所有等级
aa.debug 代码内等级 >= debug
aa.=debug 代码内等级 == debug
aa.!debug 代码内等级!=deubg

分类匹配

分类必须由数字和字母组成,下划线"_"也算字母。

总结 配置文件规则分类 匹配的代码分类 不匹配的代码分类
"*"匹配所有 . aa, aa_bb, aa_cc,
xx, yy ...
NONE
以"_"结尾的分类匹配本级以下级分类 aa_.* aa, aa_bb, aa_cc,
aa_bb_cc
xx, yy
不以"_"结尾的精确匹配分类名 aa.* aa aa_bb, aa_cc, aa_bb_cc
"!"匹配那些没有找到规则的分类 !.* xx aa(as it matches rules above)

输出动作

目前zlog支持若干种输出,语法:

[输出],[附加选项, 可选]; [format(格式)名, 可选]
动作 输出字段 附加选项
标准输出 >stdout 无意义
标准错误输出 >stderr 无意义
输出到syslog >syslog syslog设施(facility):
LOG_USER(default), LOG_LOCAL[0-7]必填
管道输出 cat
文件 "文件路径" 文件转档(见下文)
10M*3 ~ "press.#r.log"
同步IO文件 -"文件路径"
用户自定义输出 $name "path"动态或静态地用于record输出
  • stdout, stderr, syslog

如表格描述,其中只有syslog的附加选项是有意义并且必须写到。
值得注意的是,zlog在写日志的时候,会用这样的语句:

write(STDOUT_FILENO, zlog_buf_str(a_thread->msg_buf), zlog_buf_len(a_thread->msg_buf));

如果你的程序是一个守护进程,那么在启动时候就会把STDOUT_FILENO,也就说值为1的文件描述符关闭,会发生什么?
日志会被写到新的值为1的文件描述符所代表的文件里。有用户就反馈过,zlog把日志写到自己的配置文件里面去了。

因此,不要在守护进程的规则里加上>stdout或>stderr。这会产生未定义行为。如果一定要输出到终端(STDOUT_FILENO),那么用"/dev/tty"代替。

  • 管道输出
*.*    | /usr/bin/cronolog /www/logs/example_%Y%m%d.log ; normal

这是一个将zlog的输出到管道后接cronolog的例子。实现原理很简单,在zlog_init时,调用popen("/usr/bin/cronolog /www/logs/example_%Y%m%d.log", "w"),后面往这个文件描述符写指定格式的日志。使用cronolog来生成按天分割的日志效率,比zlog自己的动态路径的效率要高,因为通过管道,无需每次都打开、关闭动态路径的文件描述符。

[rules]
*.*              "press%d(%Y%m%d).log"
$ time ./test_press_zlog 1 10 100000
real 0m4.240s
user 0m2.500s
sys 0m5.460s

[rules]
*.*                  / /usr/bin/cronolog press%Y%m%d.log
$ time ./test_press_zlog 1 10 100000
real 0m1.911s
user 0m1.980s
sys 0m1.470s

使用管道也有限制:
1)POSIX.1-2001 保证读写不大于PIPE_BUF大小的内容是原子的。Linux上,PIPE_BUF为4096。
2)单条日志的长度超过PIPE_BUF时,并且有多个有父子关系的进程通zlog写同一个管道,也就说zlog_init之后fork多个子进程,此时只有一个cronolog的进程监听一个管道描述符,日志内容可能会交错。
3)多个进程分别调用zlog_init,启动多个cronolog进程,写拥有同一个文件路径的日志文件,即单条日志长度不超过PIPE_BUF,也有可能导致日志交错,因为cronolog读到的文件流是连续的,它不知道单条日志的边界在哪。

总结下,使用管道来输出到单个日志文件的情况:
1)单进程写,单条日志长度不限制。单进程内的多线程写日志的原子性已经由zlog保证。
2)有父子关系的多进程,单条日志长度不能超过PIPE_BUF(4096)。
3)无父子关系的多进程使用管道同时写一个日志,无论单条日志长度是多少,都有可能导致日志交错。

zlog本身的直接文件输出能保证即使多进程,同时调用zlog写一个日志文件也不会产生交错。

  • 文件

1)文件路径
可以说相对路径或绝对路径,被双引号("")包含。转换格式串可以用在文件路径上。例如,文件路径是"%E(HOME)/log/out.log",环境变量$HOME是/home/harry,最后的输出文件是/home/harry/log/output.log。详见【转换格式串】部分。

zlog文件功能强大,例如:
(1)输出到命名管道(FIFO),必须在调用前由mkfifo(1)创建

*.*    "/tmp/pipefile"

(2)输出到NULL,也就是不输出

*.*    "/dev/null"

(3)在任何情况下输出到终端

*.*    "/dev/tty"

(4)每个线程一个日志,在程序运行的目录下

*.*     "%T.log"

(5)输出到有进程号区分的日志,每天,在$HOME/log目录,每1GB转档一次,保持5个日志文件。

*.*     "%E(HOME)/log/aa.%p.%d(%F).log",1GB * 5

(6)aa_及下级分类,每个分类一个日志

aa_.*      "/var/log/%c.log"
  • 文件转档

控制文件的大小和个数。zlog根据这个字段来转档,当日志文件太大时,例如

"%E(HOME)/log/out.log", 1M * 3 ~ "%E(HOME)/log/out.log.#r"

这三个参数都不是必填项,zlog转档功能详见【文件转档】。

  • 同步IO文件

在文件路径前加一个"-",就打开了同步IO选项 。在打开文件(open)时,会以O_SYNC选项打开,此时,每次写日志操作都会等OS把数据写到硬盘后才返回。该选项极为耗时:

$ time ./test_press_zlog 100 1000
real 0m0.732s
user 0m1.030s
sys 0m1.080s
$ time ./test_press_zlog 100 1000 # synchronous I/O open
real 0m20.646s
user 0m2.570s
sys 0m6.950s
  • 格式名

可选。如果不写,用全局配置里面的默认格式

[global]
default format = "%d(%F %T) %V [%p:%F:%L] %m%n"
  • 用户自定义输出

详见【用户自定义输出】部分。


文件转档

为什么需要日志文件转档?
因为日志文件过大,可能导致系统硬盘被撑爆;或者,单个日志文件过大,导致grep需要花费很多时间来查找需要匹配的日志。

日志转档,有以下范式:

1)按固定时间段来切分日志

如每天生成一个日志文件

aa.2022-08-02.log
aa.2022-08-03.log
aa.2022-08-04.log

适用场景:管理员大概知道每天生成日志量,然后希望在n个月后,能精确找到某天所有日志。这种日志切分最好由日志库来完成,其次由cronosplit软件分析日志内容的时间字符串后进行切分,最后是用crontab+logrotate或mv来定期移动(但不精确,会造成若干条当前日志被放到上一天的文件里面去)。

zlog完成这种需求,很简单,只需要在(输出)日志文件名里设置时间日期字符串就行了

*.* "aa.%d(%F).log"

如果用cronolog,速度会更快点

*.* | cronolog aa.%F.log

2)按照日志大小切分

多用于开发环境,适用场景:程序在短时间生成大量日志,而vi、ue等编辑器打开速度过慢。同样地,这种日志切分可用在事后用split等工具来完成,但对于开发而言会增加步骤,所以最好由日志库来完成。

存档有两种模式:nlog里称为Sequence和Rolling。

Sequence的例子:

aa.log (new)
aa.log.2 (less new)
aa.log.1
aa.log.0 (old)

Rolling的例子:

aa.log (new)
aa.log.0 (less new)
aa.log.1
aa.log.2 (old)

其中,aa.log永远是当前最新的log。Sequence和Rolling的区别,就是后缀0,1,2,谁是次新的,谁是最旧的。

如果只有若干个最新的文件才有意义,需要日志库做主动的删除旧log的工作。由外部程序判断哪些是旧的很难。

最简单的zlog转档配置

[rules]
*.*     "aa.log", 10MB

该配置是Rolling的情况,每次aa.log超过10MB的时候,会这样做重命名:

aa.log.2 -> aa.log.3
aa.log.1 -> aa.log.2
aa.log.0 -> aa.log.1
aa.log   -> aa.log.0

上面配置可用写更啰嗦一点:

[rules]
*.*     "aa.log", 10MB * 0 ~ "aa.log.#r"

逗号后,第一个参数表示文件达到多大后,开始进行转档;
第二个参数表示保留多少个存档文件(0表示不删除任何存档文件);
第三个参数表示转档的文件名,其中#r表示存档文件的序号,r是rolling的缩写。还可以放#s,s是sequence缩写。转档文件名必须包含#r或#s。

3)按照日志大小切分,但同时加上时间标签

aa.log
aa.log-20070305.00.log
aa.log-20070501.00.log
aa.log-20070501.01.log
aa.log-20071008.00.log

这种情况适合于程序本身的日志不是很受关注,但又在某天想要找出来看看。当然,这种情况下,万一在20070501这天日志的量超过指定值,如100MB,就要退回到第二种状态,在文件名种加后缀。

注:第二种状态,纯粹看日志大小进行切分,有可能多天的日志都混在一起。

zlog对应配置:

*.*     "aa.log", 100MB ~ "aa-%d(%Y%m%d).#2s.log"

每到100MB时转档,转档文件名支持转换字符,可用把转档当时的时间串作为转档文件名的一部分。#2s的意思是序号长度最少2位,从00开始编号,Sequence转档。

4)压缩、移动、删除旧的日志

压缩不应该由日志库来完成,因为压缩消耗大量CPU时间。日志库的任务是配合压缩。

对于第一种和第三种,管理较为简单,只要符合某些文件名规则或修改日期的,用shell脚本+crontab轻易压缩、移动和删除。

如果一定要转档的同时进行压缩,只有logrotate能干这活,毕竟是独立的程序,能转档同时搞压缩,不会混淆。

5)zlog对外部转档工具,如logrotate的支持

zlog的转档功能极为强大,有几种是zlog无法处理的,如按时间条件进行转档,转档前后调用一些自制的shell脚本... 这会把zlog配置和表达弄得过于复杂而缺乏美感。

此时,你也许喜欢用一些外部转档工具,如logrotate来完成工作。问题是,Linux OS下,转档工具重命名日志文件名后,APP还是往原来的文件描述符写日志,没办法重新打开日志文件写新的日志。标准做法是给APP一个信号,让其重新打开日志文件,对syslog是

$ kill -SIGHUP 'cat /var/run/syslogd.pid'

对于zlog,因为是个函数库,不适合接受信号。zlog提供函数接口zlog_reload(),该函数会重载配置文件,重新打开所有的日志文件。APP在收到信号logrotate的信号,或者相关命令后,可用调用该函数,重新打开所有日志文件。


配置文件工具

zlog源码src路径下,有个zlog-chk-conf.c源码文件,执行"$ make"命令编译后,会生成zlog-chk-conf目标程序。该程序尝试读取配置文件,检查语法,然后往屏幕上输出这些配置文件是否正确。建议每次创建或改动一个配置文件后,都用这个工具检查一下。

$ ./zlog-chk-conf -h
usage: zlog-chk-conf [conf files]...
        -q,     suppress non-error message
        -h,     show help message
zlog version: 1.2.12

用zlog-chk-conf检查配置文件zlog.conf(error):

$ ./zlog-chk-conf zlog.conf
03-08 15:35:44 ERROR (10595:rule.c:391) sscanf [aaa] fail, category or level is null
03-08 15:35:44 ERROR (10595:conf.c:155) zlog_rule_new fail [aaa]
03-08 15:35:44 ERROR (10595:conf.c:258) parse configure file[zlog.conf] line[126] fail
03-08 15:35:44 ERROR (10595:conf.c:306) zlog_conf_read_config fail
03-08 15:35:44 ERROR (10595:conf.c:366) zlog_conf_build fail
03-08 15:35:44 ERROR (10595:zlog.c:66) conf_file[zlog.conf], init conf fail
03-08 15:35:44 ERROR (10595:zlog.c:131) zlog_init_inner[zlog.conf] fail
  
---[zlog.conf] syntax error, see error message above

可以同时分析多个配置文件(OK):

$ ./zlog-chk-conf ../test/test_hello.conf ../test/test_init.conf
--[../test/test_hello.conf] syntax right
--[../test/test_init.conf] syntax right
posted @ 2022-09-12 00:59  明明1109  阅读(4812)  评论(0编辑  收藏  举报