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. 希望新文件可以被压缩,以节省磁盘空间或带宽。

posted @ 2022-09-14 16:34  明明1109  阅读(1025)  评论(0编辑  收藏  举报