C语言日志类库 zlog 使用指南(第五章 配置文件)

第五章 配置文件

zlog库的大多数操作都依赖于配置文件:在哪里输出日志、如何轮转日志文件、如何格式化输出等。配置文件使用专用的DSL(领域特定语言)来控制库的操作。以下是一个示例的zlog.conf配置文件:

bash 复制代码
# 注释
[global]
strict init = true
reload conf period = 1M
buffer min = 1024
buffer max = 2MB
rotate lock file = /tmp/zlog.lock
default format = "%d.%ms %-6V (%c:%F:%L) - %m%n"
file perms = 600
fsync period = 1K

[levels]
TRACE = 10
CRIT = 130, LOG_CRIT

[formats]
simple = "%m%n"
normal = "%d(%F %T) %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;

配置文件的结构如下:

  1. [] 表示一个部分的开始,各部分的顺序是固定的,按照 global-levels-formats-rules 的顺序。

关于单位的注意事项:

  • 当需要指定内存大小或大数字时,可以使用常见的单位形式,如 1k、5GB、4M 等:
  • 1k 表示 1000 字节
  • 1kb 表示 1024 字节
  • 1m 表示 1000000 字节
  • 1mb 表示 1024*1024 字节
  • 1g 表示 1000000000 字节
  • 1gb 表示 1024*1024*1024 字节
  • 单位不区分大小写,所以 1GB、1Gb、1gB 是一样的。

关键配置项解释:

[global] 部分:
  • strict init:设为 true 时,严格初始化。
  • reload conf period:配置文件重新加载周期。
  • buffer minbuffer max:缓冲区的最小和最大大小。
  • rotate lock file:日志旋转锁文件的位置。
  • default format:默认的日志输出格式。
  • file perms:文件权限设置。
  • fsync period:同步周期。
[levels] 部分:
  • 定义日志级别,如 TRACE 和 CRIT 等。
[formats] 部分:
  • 定义日志输出格式,如 simple 和 normal 等。
[rules] 部分:
  • 定义日志输出规则。例如:
  • default.* >stdout; simple:默认情况下,所有日志输出到标准输出,使用 simple 格式。
  • my_dog.=DEBUG >syslog, LOG_LOCAL0; simple:将 my_dog 分类的 DEBUG 日志输出到 syslog,使用 simple 格式。

通过这些配置,用户可以灵活地控制 zlog 库的日志输出行为。

5.1 全局

以下是 zlog 用户指南中关于全局配置部分的总结:

全局(Global)配置节

全局配置节以 [global] 开头,这一部分可以省略。每个配置项的语法是 (key) = (value)

1. strict init
  • strict init = true:zlog_init() 会严格检查所有格式和规则的语法,任何错误都会导致 zlog_init() 失败并返回 -1。
  • strict init = false:zlog_init() 会忽略格式和规则的语法错误。默认值为 true。
2. reload conf period
  • 自动重新加载配置文件的周期(以每个进程的日志次数为单位)。到达指定次数后,自动调用 zlog_reload()。
  • 默认值是 0,表示从不自动重新加载。
3. buffer min 和 buffer max
  • 每个线程的日志缓冲区大小:
  • buffer min:初始化时分配的缓冲区大小。
  • buffer max:扩展缓冲区的最大值。超过此值的日志内容会被截断。
  • 默认:buffer min是 1K,buffer max是 2MB。
4. rotate lock file
  • 用于在多进程环境中安全地旋转日志文件的锁文件路径。
  • 默认值为 self,表示使用配置文件本身作为锁文件。
5. default format
  • 未指定格式的规则使用的默认格式。
  • 默认值:"%d %V [%p:%F:%L] %m%n",输出示例:2012-02-14 17:03:12 INFO [3758:test_hello.c:39] hello, zlog
6. file perms
  • 创建日志文件时的文件权限(受用户的 umask 影响)。
  • 默认值为 600,意味着仅用户可以读取和写入。
7. fsync period
  • 每个规则(仅限文件)记录一定次数的日志后,调用 fsync(3) 告诉操作系统立即将数据写入磁盘。
  • 默认值为 0,表示由操作系统自行决定何时刷新输出缓冲区。

示例

  • fsync period 的效果可以显著影响程序的性能和数据安全性。频繁调用 fsync 会大大降低性能,但能确保日志迅速写入磁盘。
shell 复制代码
$ 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

通过这些全局配置,您可以根据实际的需求调整 zlog 的行为,以便更好地适应不同的日志处理场景。

5.2 日志级别 (Levels)

  • 定义部分 :此部分以 [levels] 开始,允许用户定义应用程序的日志级别。

  • 匹配宏:这些值需要与源文件中的用户定义宏匹配。

  • 语法

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

例如:

DEBUG = 1, LOG_DEBUG
  • 级别范围level int 应在 [1,253] 范围内,数值越高,重要性越大。syslog 级别是可选的,如果未设置,则使用 LOG_DEBUG

5.3 格式 (Formats)

  • 定义部分 :此部分以 [formats] 开始,用户可以定义首选的日志模式。

  • 语法

    (name) = "(actual formats)"

例如:

detailed = "%d(%m-%d %T) %-5V [%p:%F:%L] %m%n"
  • 用途name 将在接下来的 [rules] 部分中使用。该名称由字母、数字和下划线 _ 组成。actual format 应放在引号中,通过转换模式 (conversion patterns) 构建。

5.4 转换模式 (Conversion pattern)

  • 类似C语言 printf 函数 :转换模式与 C 语言的 printf 函数的转换模式密切相关。它由文本和格式控制表达式(称为转换说明符)组成。
  • 用途:转换模式用于规则的文件路径和格式的模式中。
  • 文本插入:可以在转换模式中插入任意文本。
  • 转换说明符

每个转换说明符都以百分号 (%) 开头,后跟可选的格式修饰符和一个转换字符。转换字符指定数据的类型,如类别、日志级别、日期、线程ID等。格式修饰符控制字段宽度、填充、左右对齐等。

例如:

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

这段格式定义了日志的日期、时间、日志级别、进程ID、文件名、行号以及实际的日志消息。

示例执行:

c 复制代码
zlog_info(c, "hello, zlog");

会输出:

shell 复制代码
02-14 17:17:42 INFO [4935:test_hello.c:39] hello, zlog

%+5V 意味着日志事件的级别左对齐宽度为五个字符。

总结以上内容有助于理解 zlog 配置的基本结构和它的格式化日志输出机制。这些配置选项让你能高度自定义日志输出的内容和格式。

5.4.1 转换字符
  1. %c
  • 效果:输出日志事件的类别。
  • 示例aa_bb
  1. %d()
  • 效果 :输出日志事件的日期。可以跟随日期格式说明符,例如%d(%F)%d(%m-%d %T)。如果没有指定格式,则默认%d(%F %T)。日期格式与strftime一致。
  • 示例
  • %d(%F)2011-12-01
  • %d(%m-%d %T)12-01 17:17:42
  • %d(%T).%ms17:17:42.035
  • %d%d()2012-02-14 17:03:12
  1. %E()
  • 效果:输出环境变量的值。
  • 示例%E(LOGNAME) -> simpson
  1. %ms
  • 效果:输出3位数的毫秒数。
  • 示例013
  1. %us
  • 效果:输出6位数的微秒数。
  • 示例002323
  1. %F
  • 效果 :输出发出日志请求的文件名,来自__FILE__宏。
  • 示例test_hello.c或绝对路径,如/home/zlog/src/test/test_hello.c
  1. %f
  • 效果 :输出源文件名,仅保留%F后的最后一个/后的部分。
  • 示例test_hello.c
  1. %g()
  • 效果:以UTC时间而非本地时间输出日志事件日期。
  • 示例
  • %g(%F)2011-12-01
  • %g(%m-%d %T)12-01 15:17:42
  • %g(%T).%ms15:17:42.035
  • %g%g()2012-02-14 15:03:12
  1. %H
  • 效果 :输出系统的主机名,来自gethostname
  • 示例zlog-dev
  1. %k
  • 效果 :输出内核线程ID,Linux使用syscall(SYS_gettid),OSX使用pthread_threadid_np
  • 示例2136
  1. %L
  • 效果 :输出发出日志请求的行号,来自__LINE__宏。
  • 示例135
  1. %m
  • 效果:输出与日志事件关联的应用程序提供的信息。
  • 示例hello, zlog
  1. %M(key)
  • 效果:输出与线程生成的日志事件相关的MDC(映射诊断上下文)。需要跟随一个键名。
  • 示例%M(clientNumber) -> 12345
  1. %n
  • 效果:输出Unix换行符,暂不支持MS-Windows行分隔符。
  • 示例\n
  1. %p
  • 效果 :输出生成日志事件的进程ID,来自getpid`。
  • 示例2134
  1. %U
  • 效果 :输出发出日志请求的函数名,来自__func__(C99)或__FUNCTION__(gcc)宏。
  • 示例main
  1. %V
  • 效果:输出日志事件的级别(大写)。
  • 示例INFO
  1. %v
  • 效果:输出日志事件的级别(小写)。
  • 示例info
  1. %t
  • 效果 :输出生成日志事件的线程ID的16进制格式,来自pthread_self
  • 示例ba01e700
  1. %T
  • 效果:等同于%t,但输出长格式。
  • 示例140633234859776
  1. %%
  • 效果:输出一个百分号。
  • 示例%
  1. %[other char]
  • 效果 :解析为错误语法,导致zlog_init()失败。

了解这些转换字符,可以更灵活地控制zlog的日志输出格式,使日志信息更加清晰和易于调试。

5.4.2 格式修饰符

在zlog中,格式修饰符(Format Modifier)允许你在输出日志信息时,对信息的最小字段宽度、最大字段宽度以及对齐方式进行调整。这种操作会对每个日志事件产生轻微的性能影响。接下来,我们详细介绍这些格式修饰符的使用和效果。

格式修饰符的位置

格式修饰符是可选的,位于百分号(%)和转换字符之间。

常见格式修饰符
  1. 左对齐标志:使用"-"符号。如果没有该标志,默认是右对齐(即左填充)。
  2. 最小字段宽度:一个十进制常量,表示要输出的最小字符数。如果数据项的字符数少于这个宽度,会在左侧或右侧填充空格(或其他填充字符)。默认情况下是左侧填充空格(右对齐)。
  3. 最大字段宽度:用一个点号(.)和一个十进制常量表示。如果数据项的字符数超过这个最大宽度,会从数据项的开头截断多余的字符。
格式修饰符的示例说明
格式修饰符 左对齐 最小宽度 最大宽度 说明
%20c 20 如果分类名称少于20个字符,会在左侧填充空格
%-20c 20 如果分类名称少于20个字符,会在右侧填充空格
%020c 20 如果分类名称少于20个字符,会在左侧填充0
%.30c N/A 30 如果分类名称超过30个字符,会从末尾截断
%20.30c 20 30 如果分类名称少于20个字符,会在左侧填充空格;如果分类名称超过30个字符,会从末尾截断
%-20.30c 20 30 如果分类名称少于20个字符,会在右侧填充空格;如果分类名称超过30个字符,会从末尾截断
示例解析
  • %20c:假设分类名称是"zlog",输出结果为" zlog"(左侧填充空格,总长度为20)。
  • %-20c:假设分类名称是"zlog",输出结果为"zlog "(右侧填充空格,总长度为20)。
  • %020c:假设分类名称是"zlog",输出结果为"0000000000000000zlog"(左侧填充0,总长度为20)。
  • %.30c:假设分类名称是"this_is_a_very_long_category_name",输出结果为"this_is_a_very_long_category_"(超过30个字符,从末尾截断)。
  • %20.30c:假设分类名称是"short",输出结果为" short"(左侧填充空格,总长度为20);假设分类名称是"this_is_a_very_long_category_name",输出结果为"this_is_a_very_long_category_"(超过30个字符,从末尾截断)。
  • %-20.30c:假设分类名称是"short",输出结果为"short "(右侧填充空格,总长度为20);假设分类名称是"this_is_a_very_long_category_name",输出结果为"this_is_a_very_long_category_"(超过30个字符,从末尾截断)。
总结

使用zlog的格式修饰符可以灵活地调整日志信息的显示格式,使其更加美观和易读。不过,务必注意这种操作会导致轻微的性能损耗,因此需要根据实际需要合理使用。

5.4.3 时间字符

在使用 zlog 时,时间字符支持由转换字符 d 来实现。所有的时间字符都是由 C 语言库中 strftime(3) 函数支持的。下面是我的 Linux 系统中支持的时间字符:

字符 效果 显例
%a 当前区域设置下面的简写星期几名称。 Wed
%A 当前区域设置下面的完整星期几名称。 Wednesday
%b 当前区域设置下面的简写月份名称。 Mar
%B 当前区域设置下面的完整月份名称。 March
%c 当前区域设置下面的首选日期和时间表示。 Thu Feb 16 14:16:35 2012
%C 世纪数字(年份/100)作为两个数字的整数来表示。(SU) 20
%d 当月的某一天作为十进制数(范围01到31)。 06
%D 相当于%m/%d/%y(仅对美国用户有效,美国用户需注意在其他国家,格式%d/%m/%y更常见,在国际上下文中这种格式是模糊不清的,不应使用)(SU) 02/16/12
%e 类似%d,但替换前导零为空格。(SU) 6
%F 相当于%Y-%m-%d(ISO 8601日期格式)(C99) 2012-02-16
%G ISO 8601基于星期的年份(参见备注),用世纪作为十进制数表示。ISO星期号对应的4位年份(参见%V)。这个格式和数值与%Y相同,但如果ISO星期号属于上一个年度或下一个年度,则使用那一年。(TZ) 2012
%g 类似%G,但没有世纪,是以两位年份(00-99)表示。(TZ) 12
%h 相当于%b。(SU) Feb
%H 以十进制数表示的小时数,使用24小时制(范围00到23)。 14
%I 以十进制数表示的小时数,使用12小时制(范围01到12)。 02
%j 以十进制数表示的年中的某一天(范围001到366)。 047
%k 以十进制数表示的小时数(24小时制,范围0到23),单个数字前加一个空格(参见%H)(TZ) 15
%l 以十进制数表示的小时数(12小时制,范围1到12),单个数字前加一个空格。(参见%I)(TZ) 3
%m 以十进制数表示的月份(范围01到12)。 02
%M 以十进制数表示的分钟数(范围00到59)。 11
%n 换行符。(SU) \n
%p 根据给定的时间值,输出 "AM" 或 "PM",或当前区域设置的相应字符串。中午输出 "PM",午夜输出 "AM"。 PM
%P 类似%p,但为小写形式: "am" 或 "pm",或者当前区域设置的相应字符串。(GNU) pm
%r 以.a.m. 或 p.m. 表示的时间。在POSIX区域设置下这相当于%I:%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 制表符。(SU)
%T 24小时制的时间(%H:%M:%S)。(SU) 15:14:47
%u 以十进制数表示的星期几,范围为1到7,星期一是1。参见%w。(SU) 4
%U 当前年份的星期数,以十进制数表示,范围为00到53,从第一个星期天为星期的第一天开始算第一周。参见%V和%W。 07
%V 当前年份的ISO 8601星期号,范围为01到53,第1周是包含至少4天的新一年的第一周。参见%U和%W。(SU) 07
%w 以十进制数表示的星期几,范围为0到6,星期天是0。参见%u。 4
%W 当前年份的星期数,以十进制数表示,范围为00到53,从第一个星期一为星期的第一天开始算第一周。 07
%x 当前区域设置中首选的日期表示,不包含时间。 02/16/12
%X 当前区域设置中首选的时间表示,不包含日期。 15:14:47
%y 两位数字表示的年份,范围00到99。 12
%Y 含有世纪的十进制年份。 2012
%z 时区与GMT的小时偏移量。为发出符合RFC 822的日期所需(使用"%a, %d %b %Y %H:%M:%S %z")。(GNU) +0800
%Z 时区或名称或缩写。 CST
%% 字面意义的 '%'。 %

5.5 规则 (Rules)

规则部分定义了日志信息如何被过滤、格式化和输出。此部分可以省略,但是如果省略了将不会有任何日志输出。规则的语法如下:

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

例如:

main.*               >stdout;
main.INFO            /var/log/main.log, 10MB * 5; debug_format 

在调用zlog_init()函数时,所有的规则都会被读取进内存。当调用zlog_get_category()时,多个匹配的规则会被分配给各个类别。当进行日志记录时,日志的等级会与对应的规则进行匹配,以决定该条日志是否会被输出。如果调用zlog_reload(),配置文件会重新读取进内存,并且所有的类别规则将会重新计算。

5.5.1. 等级匹配 (Level Matching)

zlog默认支持六种日志等级:"DEBUG"、"INFO"、"NOTICE"、"WARN"、"ERROR" 和 "FATAL"。在配置文件中,等级不区分大小写;例如,"DEBUG"和"debug"是等价的。

匹配表达式举例如下:

例子 含义
* 所有日志等级
aa.debug 日志等级>=debug
aa.=debug 日志等级==debug
aa.!debug 日志等级!=debug

用户可以定义自定义的等级,具体请参考相关章节(User-defined Level)。

5.5.2. 类别匹配 (Category Matching)

类别匹配非常简单。类别名称由字母、数字和下划线组成:

描述 配置文件中的类别字符串 类别匹配 类别不匹配
* 匹配所有类别 . aa, aa_bb, 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(因为上面匹配了规则)
5.5.3. 输出操作 (Output Action)

zlog支持多种输出方法,语法如下:

(output action), (output option); (format name, optional)

例如:

*.*   >stdout;
*.*   | /usr/bin/cronolog /www/logs/example_%Y%m%d.log; normal
*.*   "/var/log/output.log";

表格描述了不同输出动作和选项:

输出类型 输出动作 输出选项
到标准输出 >stdout 无意义
到标准错误 >stderr 无意义
到syslog >syslog syslog facility, 必填
管道输出 cat 无意义
到文件 "(文件路径)" 支持旋转,具体见下文
同步I/O到文件 -"(文件路径)"
用户自定义输出 $name 记录函数的路径(动态或静态)

标准输出/标准错误,以守护进程运行时要非常小心,不建议设置>stdout或>stderr,因为守护进程会关闭其首个文件描述符,可能导致日志写入错误的文件。

5.5.4. 文件输出和日志轮转

zlog的文件输出非常强大,可以设置文件路径(支持相对和绝对路径)和旋转规则。例如:

*.*   "/var/log/app.log", 1MB * 3

表示文件的大小达到1MB时,会触发日志轮转,最多保留3个日志文件。

5.5.5. 同步I/O文件

设置同步I/O选项(在文件路径前加'-'),例如:

*.*   -"/var/log/app.log"

将确保每次日志写入操作等待系统将数据写入磁盘,尽管这会显著降低性能。

5.5.6. 自定义输出格式

zlog默认的输出格式在全局设置中定义:

[global]
default format = "%d %V [%p:%F:%L] %m%n"

也可以在规则中自定义格式,例如:

main.INFO            /var/log/main.log, 10MB * 5; debug_format
补充例子:

在使用 zlog 进行日志管理时,有几个重要的输出方式需要了解,包括 stdout、stderr、syslog、管道输出和文件输出。以下是这些输出方式的详细解释:

stdout, stderr, syslog

在 zlog 中,只有 syslog 需要指定一个有意义的输出选项。此外,还有一些关于守护进程的警告:

警告绝不要 在守护进程中使用 >stdout>stderr。因为守护进程会关闭它的第一个文件描述符。如果设置了 >stdout,zlog 将输出日志到文件描述符1。这会导致日志被写入到意想不到的文件里,比如配置文件。

示例
c 复制代码
write(STDOUT_FILENO, zlog_buf_str(a_thread->msg_buf), zlog_buf_len(a_thread->msg_buf));

这种情况下,日志文件将被写入文件描述符1。因此,守护进程不应该将任何规则输出设为 stdout 或 stderr,以避免未定义的行为。如果确实需要在 stdout 关闭时输出日志到控制台,可以使用 /dev/tty

管道输出 (pipeline output)
示例规则
plaintext 复制代码
*.* | /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 写日志比每次写日志时动态打开和关闭文件描述符要快。

性能对比

普通文件写入

plaintext 复制代码
[rules]

*.* "press%d(%Y%m%d).log"

$ time ./test_press_zlog 1 10 100000

real 0m4.240s
user 0m2.500s
sys 0m5.460s

通过管道写入

plaintext 复制代码
[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 字节的 write 操作必须是原子的。在 Linux 上,PIPE_BUF 是 4096 字节。
  2. 当单个日志超出 PIPE_BUF 字节,并且多个进程通过一个管道写日志时,会发生日志混杂。
  3. 不相关的多个进程写入同一个管道日志文件时,也会导致日志混杂。

总结:

  • 单进程写入时,单个日志长度无实际限制,多线程单进程中 zlog 确保了原子写入。
  • 相关联的多个进程,单个日志长度不应超过 PIPE_BUF 字节。
  • 不相关的多个进程,不论单个日志长度,会导致日志混杂。
文件输出 (file)
文件路径

可以是绝对路径或相对路径,使用双引号引用。可以在文件路径中使用转换模式。例如:

plaintext 复制代码
%E(HOME)/log/out.log

如果程序的环境变量 $HOME 是 /home/harry,那么日志文件路径将是 /home/harry/log/output.log。

文件输出示例
  1. 输出到命名管道(FIFO):
plaintext 复制代码
*.* "/tmp/pipefile"
  1. 输出到空设备,等于不记录日志:
plaintext 复制代码
*.* "/dev/null"
  1. 输出到控制台:
plaintext 复制代码
*.* "/dev/tty"
  1. 输出到每个线程ID的日志文件:
plaintext 复制代码
*.* "%T.log"
  1. 输出到以进程ID命名的文件,每天一个文件,在 $HOME/log 目录下,日志轮转设定为1GB,保留5个文件:
plaintext 复制代码
*.* "%E(HOME)/log/aa.%p.%d(%F).log",1GB*5
  1. 每个 aa_ 开头的类别,输出日志到各自的文件中:
plaintext 复制代码
aa_.* "/var/log/%c.log"
日志轮转

设定日志文件大小和数量。例如:

plaintext 复制代码
%E(HOME)/log/out.log,1M*3

当 out.log 超过 1M 时,日志轮转如下:

plaintext 复制代码
out.log -> out.log.1
out.log (新创建)

out.log.1 -> out.log.2
out.log -> out.log.1
out.log (新创建)

保留3个日志文件,超过会删除最旧的文件:

plaintext 复制代码
unlink(out.log.2)
out.log.1 -> out.log.2
out.log -> out.log.1
out.log (新创建)
同步I/O文件

带有减号 '-' 的选项表示使用同步 I/O。日志文件以 O_SYNC 方式打开,每个日志操作都会等待操作系统将数据写入磁盘。例如:

plaintext 复制代码
$ time ./test_press_zlog 100 1000

real 0m0.732s
user 1m1.030s
sys 1m1.080s

$ time ./test_press_zlog 100 1000  # 同步I/O打开

real 0m20.646s
user 0m2.570s
sys 0m6.950s
格式名

格式名是可选的。如果未设置,将使用全局设定中的默认格式:

plaintext 复制代码
[global]

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

欲了解更多自定义输出的细节,请参考用户定义输出 (User-defined-Output) 部分。

5.6 日志轮转

日志轮转的必要性

在生产环境中,不止一次看到由于硬盘被日志填满而导致系统停止工作,或者单个日志文件过大而无法打开或使用 grep 搜索内容。日志轮转和归档的几种方法如下:

1. 按日期或时间分割日志

例如,每天生成一个日志文件:

plaintext 复制代码
aa.2012-08-02.log
aa.2012-08-03.log
aa.2012-08-04.log

这种方式下,系统管理员能知道每天会产生多少日志,并且可以根据日期搜索日志文件。最好的方式是让 zlog 库处理日志拆分。另一种选择是使用 cronosplit 分析日志内容并拆分。但是,使用 crontab + logrotate 每天移动日志文件不是很准确,部分日志可能会被放到前一天的文件中。

使用 zlog 时,不需要外部旋转操作即可完成工作。可以通过在日志文件名中设置时间来实现:

plaintext 复制代码
*.*  "aa.%d(%F).log"

或者使用 cronolog 以提高性能:

plaintext 复制代码
*.*  | cronolog aa.%F.log
2. 按大小分割日志

这种方式通常适用于开发环境,在这种情况下,程序会在短时间内生成大量日志。尽管可以使用拆分工具在之后进行拆分,但这需要额外步骤。所以更好的方式是让日志库自己处理日志轮转。正如 nlog 所描述的,有两种轮转方式:序列和滚动。

  • 序列方式:
plaintext 复制代码
aa.log (新)
aa.log.2 (较新)
aa.log.1
aa.log.0 (旧)
  • 滚动方式:
plaintext 复制代码
aa.log (新)
aa.log.0 (较新)
aa.log.1
aa.log.2 (旧)

很难说哪种方式最适用。如果只有部分最新的日志对开发人员有用,日志库应该清理旧的日志文件,某些外部工具可能无法找到哪些文件是旧的。

最简单的 zlog 轮转配置如下:

plaintext 复制代码
*.*     "aa.log", 10MB

这是滚动方式。当 aa.log 大于10MB, zlog 会重命名文件如下:

plaintext 复制代码
aa.log.2 -> aa.log.3
aa.log.1 -> aa.log.2
aa.log.0 -> aa.log.1
aa.log -> aa.log.0

配置可以更复杂一些:

plaintext 复制代码
*.*     "aa.log", 10MB * 0 ~ "aa.log.#r"
  • 文件名后的第一个参数表示触发轮转的大小。
  • 文件名后的第二个参数表示保留多少归档文件,0 表示保留所有文件。
  • 文件名后的第三个参数显示归档文件的名称。#r 是归档文件的序列号。r 表示滚动,#s 表示序列。归档文件名必须包含 #r#s 之一。
3. 按大小分割日志并添加时间标签

例如:

plaintext 复制代码
aa.log
aa.log-20070305.00.log
aa.log-20070501.00.log
aa.log-20070501.01.log
aa.log-20071008.00.log

在这种情况下,日志文件通常不会经常查看,可能一天查看一次。当然,当一天的日志多于100MB时,应考虑存储到两个文件并添加后缀号。例如,如果日期用作模式的一部分(如20070501),zlog 的配置如下:

plaintext 复制代码
*.*     "aa.log", 100MB ~ "aa-%d(%Y%m%d).#2s.log"

每100MB进行一次轮转,归档文件名也支持转换字符串。#2s 意味着序列号至少有2位字节宽,从00开始。这是使用 zlog 归档最复杂的方式。

4. 压缩、移动和删除旧归档

压缩不应由日志库完成,因为压缩需要时间和CPU。日志库的任务是与压缩程序合作。

对于上述拆分日志的三种方式,方式1和方式3易于管理,可以通过文件名或修改时间找到旧的日志文件,然后使用 crontab 和 shell 脚本来压缩、移动和删除旧日志文件。

对于第二种方式,压缩没有用,删除是必须的,zlog 已经支持这一点。

如果你真的想同时轮转和压缩日志文件,建议使用 logrotate。这是一个独立的程序,不会使情况变得混乱。

5. zlog 对外部工具如 logrotate 的支持

zlog 的轮转支持非常强大,但仍有几种情况无法处理。例如按时间轮转,轮转前后调用一些用户定义的shell。这将使 zlog 变得太复杂。

在这种情况下,考虑使用外部工具如 logrotate。在 Linux 上,问题在于当一个工具重命名日志文件时,使用 inode 引用文件的工作进程不会自动重新打开新文件。标准方式是向程序发送信号并让其重新打开文件。对于 syslogd,命令如下:

plaintext 复制代码
kill -SIGHUP `cat /var/run/syslogd.pid`

对于 zlog 作为一个库来说,接收信号不是一个好办法。zlog 提供 zlog_reload() 函数,可以重新加载配置文件并重新打开所有日志文件。所以,如果你编写一个程序并想手动重新打开日志文件,可以写一些代码来完成这项工作:在接收到信号或客户的命令后,调用 zlog_reload()

5.7 配置文件工具

在使用 zlog 进行日志管理时,正确配置日志配置文件是至关重要的。zlog 提供了一个工具 zlog-chk-conf,用于检查和验证配置文件的语法是否正确。下面我将详细解释如何使用这一工具以及解读输出信息。

使用 zlog-chk-conf 工具

在命令行终端中输入以下命令可以查看 zlog-chk-conf 的使用方法:

sh 复制代码
$ zlog-chk-conf -h

使用该命令会显示以下帮助信息:

sh 复制代码
Usage: zlog-chk-conf [conf files]...

-q,  suppress non-error messages
-h,  show help message

该工具的用途是读取配置文件,检查其语法,并输出检查结果。建议每次创建或更改配置文件后,都使用这个工具进行检查,以确保配置文件没有语法错误。

检查配置文件

假设你的配置文件名为 zlog.conf,你可以使用以下命令来检查该文件:

sh 复制代码
$ ./zlog-chk-conf zlog.conf
输出示例及解释

运行上述命令后,可能会得到如下输出:

sh 复制代码
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

这些错误信息的详细解释:

  1. 错误信息:sscanf [aaa] fail, category or level is null
  • 表明在解析配置文件中的某一行 [aaa] 规则时,sscanf 函数失败了,可能是因为类别或级别为空。你需要仔细检查该行的规则定义。
  1. 错误信息:zlog_rule_new fail [aaa]
  • 由于上一步的解析失败,导致创建新的 zlog 规则失败。这是一个连锁反应。
  1. 错误信息:parse configure file[zlog.conf] line[126] fail
  • 明确指出了错误发生在配置文件的第126行。你可以直接前往该行进行排查。
  1. 错误信息:zlog_conf_read_config fail
  • 读取配置文件失败,可能因为之前的错误导致无法继续解析。
  1. 错误信息:zlog_conf_build fail
  • 构建 zlog 配置失败,持续受到之前错误的影响。
  1. 错误信息:conf_file[zlog.conf], init conf failzlog_init_inner[zlog.conf] fail
  • 最终,由于初始化配置失败,整个日志系统初始化失败。
总结

通过 zlog-chk-conf 工具,你可以快速定位和修复配置文件中的语法错误,确保你的 zlog 日志系统能够正常运行。每当你修改配置文件后,建议运行上述检查命令,避免潜在的配置错误影响系统的日志功能。

相关推荐
DanielYQ9 分钟前
LCR 001 两数相除
开发语言·python·算法
yngsqq14 分钟前
037集——JoinEntities连接多段线polyline和圆弧arc(CAD—C#二次开发入门)
开发语言·c#·swift
冉佳驹16 分钟前
数据结构 ——— 希尔排序算法的实现
c语言·数据结构·算法·排序算法·希尔排序
Zԅ(¯ㅂ¯ԅ)17 分钟前
C#桌面应用制作计算器进阶版01
开发语言·c#
过期的H2O218 分钟前
【H2O2|全栈】JS进阶知识(七)ES6(3)
开发语言·javascript·es6
一路冰雨29 分钟前
Qt打开文件对话框选择文件之后弹出两次
开发语言·qt
St_Ludwig31 分钟前
C语言 蓝桥杯某例题解决方案(查找完数)
c语言·c++·后端·算法·游戏·蓝桥杯
是糖不是唐33 分钟前
代码随想录算法训练营第五十三天|Day53 图论
c语言·数据结构·算法·图论
松树戈37 分钟前
JS推荐实践
开发语言·javascript·ecmascript
瑞雨溪40 分钟前
java中的this关键字
java·开发语言