zlog日志库(笔记) —— 高阶功能使用
MDC
MDC在log4j里解释为Mapped Diagnostic Context,其实就是一个键-值对表。一旦设置了,后面库可以帮你自动打印出来,或者成为文件名的一部分。
来看一个例子,来源于test/test_mdc.c
// test/test_mdc.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include "zlog.h"
int main(int argc, char** argv)
{
int rc;
zlog_category_t *zc;
rc = zlog_init("test_mdc.conf");
if (rc) {
printf("init failed\n");
return -1;
}
zc = zlog_get_category("my_cat");
if (!zc) {
printf("get cat fail\n");
zlog_fini();
return -2;
}
zlog_info(zc, "1.hello, zlog");
zlog_put_mdc("myname", "Zhang");
zlog_info(zc, "2.hello, zlog");
zlog_put_mdc("myname", "Li");
zlog_info(zc, "3.hello, zlog");
zlog_fini();
return 0;
}
对应配置文件test/test_mdc.conf
[formats]
mdc_format= "%d(%F %X.%ms) %-6V (%c:%F:%L) [%M(myname)] - %m%n"
[rules]
*.* >stdout; mdc_format
运行输出:
$ ./test_mdc
2022-09-12 10:50:50.09s INFO (my_cat:test_mdc.c:36) [] - 1.hello, zlog
2022-09-12 10:50:50.09s INFO (my_cat:test_mdc.c:40) [Zhang] - 2.hello, zlog
2022-09-12 10:50:50.09s INFO (my_cat:test_mdc.c:44) [Li] - 3.hello, zlog
zlog_put_mdc()在表里设置键"myname",值"Zhang"然后,在配置文件中,"%M(myname)"指出在日志的哪个位置需要把值打印出来。第二次调用zlog_put_mdc(),键"myname"的值被覆盖写成"Li",日志里也有相应的变化。
MDC何时有用?
往往在用户需要在同样的日志行为,用来区分不同的业务数据到时候,比如C源码:
zlog_put_mdc("customer_name", get_customer_name_from_db());
zlog_info("get in");
zlog_info("pick product");
zlog_info("pay");
zlog_info("get out");
配置文件中对应内容:
[formats]
&format "%M(customer_name) %m%n"
当程序同时处理两个用户的时候,打印出来的日志可能是:
Zhang get in
Li get in
Zhang pick product
Zhang pay
Li pick product
Li pay
Zhang get out
Li get out
而如果使用了MDC,就可以通过grep命令将这2个用户的日志区分开:
$ grep Zhang aa.log > Zhang.log
$ grep Li aa.log > Li.log
或者,一开始在文件名里面做区分:
*.* "mdc_%M(customer_name).log"
这样会产生3个日志文件。
mdc_.log mdc_Zhang.log mdc_Li.log
MDC是每个线程独有的,因此可以把一些线程专有变量设置进去。如果仅仅是为了区分不同线程,可以用转换字符%t来搞定(见【转换字符】)。
诊断zlog本身
1)打开zlog诊断日志
zlog库帮助用户写日志,如果APP出现问题,可以通过zlog记录下来。但如果zlog内部出现问题呢?如何判断哪里出错?
可以通过zlog的诊断日志。通常,该日志是关闭的,可以通过环境变量来打开。
设置诊断log的环境变量,再启动包含zlog的APP
$ export ZLOG_PROFILE_DEBUG=/tmp/zlog.debug.log
$ export ZLOG_PROFILE_ERROR=/tmp/zlog.error.log
注:具体路径,用户可自定义,但进程用户必须在该目录下具有读写文件的权限;"="前后不能有空格;
诊断日志只有2个级别:debug和error。设置好环境变量后,再运行test/test_hello,
$ more zlog.debug.log # 类似于cat命令, 区别在于前者可以分页显示
09-12 14:41:49 DEBUG (4930:zlog.c:119) ------zlog_init start------
09-12 14:41:49 DEBUG (4930:zlog.c:120) ------compile time[Sep 11 2022 23:13:35], version[1.2.12]------
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc8611660][%d(%m-%d %T)][%m-%d %T|0][,0,0,false][]---
-
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc8613ac0][ ][|0][,0,0,false][]----
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc8615f20][%-8V][|0][-8,0,8,false][]----
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc8618380][ [][|0][,0,0,false][]----
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc861a7e0][%p][|0][,0,0,false][]----
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc861cc40][:][|0][,0,0,false][]----
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc861f0a0][%F][|0][,0,0,false][]----
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc8621500][:][|0][,0,0,false][]----
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc8623960][%L][|0][,0,0,false][]----
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc8625dc0][] ][|0][,0,0,false][]----
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc8628220][%m][|0][,0,0,false][]----
09-12 14:41:49 DEBUG (4930:spec.c:35) ----spec[0x7fffc862a680][%n][|0][,0,0,false][]----
09-12 14:41:49 DEBUG (4930:format.c:25) ---format[0x7fffc860f510][simple3 = %d(%m-%d %T) %-8V [%p:%F:%L]
%m%n(0x7fffc8611530)]---
...
zlog.error.log日志没有产生,因为没有错误发生。因为如果zlog库有任何问题,会打印日志到ZLOG_PROFILE_ERROR所指错误日志。
运行时,诊断功能会带来一定性能损失。因此,生产环境中,可以把ZLOG_PROFILE_ERROR打开,ZLOG_PROFILE_DEBUG关闭。
2)查看zlog配置信息栈
另一个诊断zlog的方法:zlog_init()会把配置信息读入内存,在整个写日志的过程中,这块内存保持不变。如果用户程序因为某种原因损坏了这块内存,那么就会造成问题。也可能是内存中的信息和配置文件的信息不匹配。zlog提供了一个函数zlog_profile,把这部分内存信息展现到ZLOG_PROFILE_ERROR错误日志。
代码参见test/test_profile.c
#include <stdio.h>
#include "zlog.h"
int main(int argc, char** argv)
{
int rc;
rc = dzlog_init("test_profile.conf", "my_cat");
if (rc) {
printf("init failed\n");
return -1;
}
dzlog_info("hello, zlog");
zlog_profile();
zlog_fini();
return 0;
}
对应配置文件test/test_profile.conf
[formats]
simple="%m%n"
[rules]
my_cat.* >stdout; simple
可以看到zlog.error.log如下:
$ cat /tmp/zlog.error.log
09-12 15:10:12 WARN (8938:zlog.c:951) ------zlog_profile start------
09-12 15:10:12 WARN (8938:zlog.c:952) is init:[1]
09-12 15:10:12 WARN (8938:zlog.c:953) init version:[2]
09-12 15:10:12 WARN (8938:conf.c:45) -conf[0x7fecda030010]-
09-12 15:10:12 WARN (8938:conf.c:46) --global--
09-12 15:10:12 WARN (8938:conf.c:47) ---file[test_profile.conf],mtime[2022-09-07 16:57:02]---
09-12 15:10:12 WARN (8938:conf.c:48) ---in-memory conf[]---
09-12 15:10:12 WARN (8938:conf.c:49) ---strict init[1]---
09-12 15:10:12 WARN (8938:conf.c:50) ---buffer min[1024]---
09-12 15:10:12 WARN (8938:conf.c:51) ---buffer max[2097152]---
09-12 15:10:12 WARN (8938:conf.c:53) ---default_format---
09-12 15:10:12 WARN (8938:format.c:25) ---format[0x7ffff7678fb0][default = %D %V [%p:%F:%L] %m%n(0x7ffff767afd0)]---
09-12 15:10:12 WARN (8938:conf.c:56) ---file perms[0600]---
09-12 15:10:12 WARN (8938:conf.c:57) ---reload conf period[0]---
09-12 15:10:12 WARN (8938:conf.c:58) ---fsync period[0]---
09-12 15:10:12 WARN (8938:conf.c:60) ---rotate lock file[test_profile.conf]---
09-12 15:10:12 WARN (8938:rotater.c:35) --rotater[0x7ffff7678b30][0x7ffff7678b30,test_profile.conf,4][(null),(null),,0,0,0,0,0]--
09-12 15:10:12 WARN (8938:level_list.c:26) --level_list[0x7ffff766c2a0]--
09-12 15:10:12 WARN (8938:level.c:20) ---level[0x7ffff766c3d0][0,*,*,1,6]---
09-12 15:10:12 WARN (8938:level.c:20) ---level[0x7ffff766cbf0][20,DEBUG,debug,5,7]---
09-12 15:10:12 WARN (8938:level.c:20) ---level[0x7ffff766d410][40,INFO,info,4,6]---
09-12 15:10:12 WARN (8938:level.c:20) ---level[0x7ffff766de40][60,NOTICE,notice,6,5]---
09-12 15:10:12 WARN (8938:level.c:20) ---level[0x7ffff766e660][80,WARN,warn,4,4]---
09-12 15:10:12 WARN (8938:level.c:20) ---level[0x7ffff766f290][100,ERROR,error,5,3]---
09-12 15:10:12 WARN (8938:level.c:20) ---level[0x7ffff766fab0][120,FATAL,fatal,5,1]---
09-12 15:10:12 WARN (8938:level.c:20) ---level[0x7ffff76702d0][254,UNKNOWN,unknown,7,3]---
09-12 15:10:12 WARN (8938:level.c:20) ---level[0x7ffff7671300][255,!,!,1,6]---
09-12 15:10:12 WARN (8938:conf.c:66) --format list[0x7ffff7671b20]--
09-12 15:10:12 WARN (8938:format.c:25) ---format[0x7ffff7672120][simple = %m%n(0x7ffff7674140)]---
09-12 15:10:12 WARN (8938:conf.c:73) --rule_list[0x7ffff7671c50]--
09-12 15:10:12 WARN (8938:rule.c:40) ---rule:[0x7ffff7696580][my_cat*0]-[384,0][,(nil),0:0*0~][0][0][::(nil)];[0x7ffff7672120]---
09-12 15:10:12 WARN (8938:record_table.c:22) -record_table[0x7ffff7698740]-
09-12 15:10:12 WARN (8938:category_table.c:22) -category_table[0x7ffff7698650]-
09-12 15:10:12 WARN (8938:category.c:23) --category[0x7ffff7698830][my_cat][0x7ffff7698ca0]--
09-12 15:10:12 WARN (8938:rule.c:40) ---rule:[0x7ffff7696580][my_cat*0]-[384,0][,(nil),0:0*0~][0][0][::(nil)];[0x7ffff7672120]---
09-12 15:10:12 WARN (8938:zlog.c:958) -default_category-
09-12 15:10:12 WARN (8938:category.c:23) --category[0x7ffff7698830][my_cat][0x7ffff7698ca0]--
09-12 15:10:12 WARN (8938:rule.c:40) ---rule:[0x7ffff7696580][my_cat*0]-[384,0][,(nil),0:0*0~][0][0][::(nil)];[0x7ffff7672120]---
09-12 15:10:12 WARN (8938:zlog.c:961) ------zlog_profile end------
用户自定义输出
用户自定义输出意义:zlog放弃一些权力,只负责动态生成单条日志和文件路径,但如何输出、转档、清理等工作由用户按自己的需求自行写函数完成。写完函数,只要绑定到某个规则即可。
自定义输出的几个步骤:
1)在配置文件里面定义规则
$ cat test_record.conf
[formats]
simple = "%m%n"
[rules]
my_cat.* $myoutput, " mypath %c %d"; simple
2)绑定一个函数到$myoutput,并使用之
#include <stdio.h>
#include "zlog.h"
int output(zlog_msg_t *msg) /* 自定义输出函数 */
{
printf("[mystd]:[%s][%s][%ld]\n", msg->path, msg->buf, (long)msg->len);
return 0;
}
int main(int argc, char *argv[])
{
int rc;
zlog_category_t *zc;
rc = zlog_init("test_record.conf");
if (rc) {
printf("init failed\n");
return -1;
}
zlog_set_record("myoutput", output);
zc = zlog_set_category("my_cat"); /* 在conf文件中根据分类名匹配规则 */
if (!zc) {
printf("get cat fail\n");
zlog_fini();
return -2;
}
zlog_info(zc, "hello, zlog");
zlog_fini();
return 0;
}
3)最后,验证测试用户自定义函数是否可用
$ ./test_record
[mystd]:[ mypath my_cat 2022-09-12 15:19:43][hello, zlog
][12]
msglen是12,而zlog生成的msg在最后会添加上一个换行符。
4)用户自定义输出可以干很多事情,譬如满足某个用户的需求:
a. 日志文件名foo.log;
b. 如果foo.log 超过100M,则生成一个新文件,其中包含的就是foo.log目前的内容,而foo.log则变成空,重新开始增长;
c. 如果距离上次生成文件时,已超过5分支,即使不到100M,也生成新文件;
d. 新文件名可以自定义,如加上设备名作为前缀,日期时间串作为后缀;
e. 希望新文件可以被压缩,以节省磁盘空间或带宽。