当前位置: 首页 > 工具软件 > zlog > 使用案例 >

日志系统模块基础、C语言实现一个日志模块、zlog日志模块基础

仲元凯
2023-12-01

一、日志系统模块基础

收集日志

日志管理的第一件事,就是日志的收集。日志收集是开发者必备的技巧,不管是哪个开发语言,哪个开发平台,日志收集的插件都是有很多选择的。例如:

.net 平台大家钟爱的log4net,支持多种存储方式(文件、数据库),多种格式,多种日志拆分方式。

java 平台主流的log4j、slf4j、logback,多种选择。

日志收集的组件这里就不一一说明了,使用都是很简单的,这里重点说明一下,日志我们收集应该注意的地方:

1. 日志等级一定要规范

等级说明debug调试信息info用来收集关注的信息warn警告信息error错误信息

好多开发工程师记录日志总是喜欢用info级别来记录日志,一般的组件默认级别都是info,所有info默认都是会被记录的,而debug信息发布后,是不会被记录的。这是一种偷懒的做法,但这也是很普遍的做法。正确的方式应该根据日志本身的特性去设置日志的级别,其实规范的日志级别是非常重要的:

  • 正确的级别便于运维。便于统一调整系统日志级别,如特殊情况可以只记录error错误
  • 没有正确的级别,对后期日志分析和处理是留下很大的隐患。error是需要去关注,并且处理掉的问题。info是普通日志的记录,大部分时候是无需关注的。

2. error日志内容一定要详实 ,info日志要简洁易懂

运营过大型系统的人都知道,除了数据库存储外,日志、图片、附件是存储的三大债主,他们是会占用非常非常大的空间,所有记录info的日志,要简洁易懂,避免空间浪费。 而对于error级别的错误,记录一定要详实,因为error的所有问题,是后期都要去解决的。

  • 请求的地址
  • 请求的参数
  • 请求的ip
  • 请求的用户
  • error具体信息
  • 输出的内容

为了能很好的反馈当时error产生场景,以上的这些内容都应该被记录,而且越详细越好。

3. error日志一定是全局统一收集的

前文说过,error的日志,不仅是我们需要关注的,还是我需要解决掉的问题,所有error日志非常重要。错误日志的收集,必须是全局统一收集的,AOP是你最好的伙伴,如果你发现你的errorr日志收集是在每个类中,到处是

try
{
......
}
catch()
{
    log.error("......")
}

这个一定要避免,不管你用那种语言,错误的处理,都是可以通过全局进行统一的处理,错误日志也要通过全局统一收集。

管理日志

每个开发人员对日志的收集,都是非常熟悉的,基本都是将日志按照日期的方式进行保存,日常使用日志的时候,也是有一些要求:

1. 单个文件的大小要控制

因为大家都是通过日期方式保存的,但是因为有的人不重视日志,经常会看到有的系统单个日志文件上百M,有的甚至是几G,而实际大家处理问题关注的都是最近的日志,所以控制单个日志文件的大小,对日志的性能以及后期的运维都是非常便利的。

2. 日志要便于浏览

日志文件小才便于浏览,日志最好能通过网址直接访问到,而不需要一波三折登录服务器,花10分钟下载下来,再来分析。

3. 日志的安全性要得到保障

日志内容有时会包含敏感信息,特别是error日志,直接把系统的具体错误抛出来,所以日志除了查看方便,还需要确保日志文件的安全。如果是日志文件是html或者txt,请一定记得把你的日志文件权限修改下,特定用户才能访问,不要随便开放,所有人都能访问。

4. 日志要定期清理

日志是非常占用存储的空间,日志太大对存储的性能也有一定的影响,所有日志要定期进行清理。

  • 空间充足可以保留半年
  • 空间不足最少也要保留3个月

当然,这个也不是一定的,根据每个系统的情况去制定清理计划就可以了。

如果大家是小型网站,一个系统一台服务器,日志管理就简单了。如果系统是做了高可用,后端用了均衡负载,那么,日志存在当前服务器是不太明智的做法,日志一定要统一存储,因为均衡负载随时都可能会切换服务器,当出现故障,你需要去找日志究竟存在哪个服务器,也是件很浪费时间的事情。日志文件也可以通过:

  • 共享虚拟目录来存储
  • 定时进行文件同步来存储
    日志存储也是对性能有一定影响的,文件同步虽然看起来麻烦一定,但是比共享虚拟目录的方式来说,性能会好,推荐使用这种方式。

说到日志的同步,就不得不提Logstash这个日志组件。Logstash是现在应用最广的日志收集组件,基于java平台。其实很多java平台的组件,是不用去了解java开发的,只要简单的配置就能使用。

Logstash支持文件同步,也可以结合rsyslog进行文件同步,当然,也支持通过tcp协议,与第三方对接,好伙伴当然是Elasticsearch。Elasticsearch下文也会做简单的介绍。

Logstash中文手册:点击这里

分析日志

日志的分析也是一个很大的概念,可能对于运维和安全人员关注的是系统的所有日志,包括访问日志、系统监测的日志等,但是开发人员对于日志更多的是:

  • 监控系统运行错误,并获取错误时的相关数据包
  • 记录重要的信息,某些时候便于后期检查

二、C语言实现一个日志系统模块

事实上,在C的世界里面没有特别好的日志函数库(就像JAVA里面的的log4j,或者C++的log4cxx)。C程序员都喜欢用自己的轮子。printf就是个挺好的轮子,但没办法通过配置改变日志的格式或者输出文件。syslog是个系统级别的轮子,不过速度慢,而且功能比较单调。log4c异常坑爹(有内存泄漏、效率低等等),而且已经停止开发。

所以,如果我们对日志系统模块的要求不是很高的话,我们可以考虑自己写一个。

1.C语言日志系统1

参考博客:用C语言打印日志(Log)

log.h 文件:

/** log.h **/
 
#ifndef __LOG_H__
#define __LOG_H__
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "time.h"
#include "stdarg.h"
#include "unistd.h"
 
#define MAXLEN (2048)
#define MAXFILEPATH (512)
#define MAXFILENAME (50)
typedef enum{
	ERROR_1=-1,
	ERROR_2=-2,
	ERROR_3=-3
}ERROR0;
 
 
typedef enum{
	NONE=0,
	INFO=1,
	DEBUG=2,
	WARN=3,
	ERROR=4,
	ALL=255
}LOGLEVEL;
 
typedef struct log{
	char logtime[20];
	char filepath[MAXFILEPATH];
	FILE *logfile;
}LOG;
 
typedef struct logseting{
	char filepath[MAXFILEPATH];
	unsigned int maxfilelen;
	unsigned char loglevel;
}LOGSET;
 
int LogWrite(unsigned char loglevel,char *fromat,...);
#endif /* __LOG_H__ */

log.c 文件:

/** log.c **/
 
#include "log.h"
#define MAXLEVELNUM (3)
 
LOGSET logsetting;
LOG loging;
 
const static char LogLevelText[4][10]={"INFO","DEBUG","WARN","ERROR"};
 
static char * getdate(char *date);
 
static unsigned char getcode(char *path){
	unsigned char code=255;
	if(strcmp("INFO",path)==0)
		code=1;
	else if(strcmp("WARN",path)==0)
		code=3;
	else if(strcmp("ERROR",path)==0)
		code=4;
	else if(strcmp("NONE",path)==0)
		code=0;
	else if(strcmp("DEBUG",path)==0)
		code=2;
	return code;
}
 
static unsigned char ReadConfig(char *path){
	char value[512]={0x0};
	char data[50]={0x0};
 
	FILE *fpath=fopen(path,"r");
	if(fpath==NULL)
		return -1;
	fscanf(fpath,"path=%s\n",value);
	getdate(data);
	strcat(data,".log");
	strcat(value,"/");
	strcat(value,data);
	if(strcmp(value,logsetting.filepath)!=0)
		memcpy(logsetting.filepath,value,strlen(value));
	memset(value,0,sizeof(value));
 
	fscanf(fpath,"level=%s\n",value);
	logsetting.loglevel=getcode(value);
	fclose(fpath);
	return 0;
}
/*
 *日志设置信息
 * */
static LOGSET *getlogset(){
	char path[512]={0x0};
	getcwd(path,sizeof(path));
	strcat(path,"/log.conf");
	if(access(path,F_OK)==0){
		if(ReadConfig(path)!=0){
			logsetting.loglevel=INFO;
			logsetting.maxfilelen=4096;
		}
	}else{
		logsetting.loglevel=INFO;
		logsetting.maxfilelen=4096;
	}
	return &logsetting;
}
 
/*
 *获取日期
 * */
static char * getdate(char *date){
	time_t timer=time(NULL);
	strftime(date,11,"%Y-%m-%d",localtime(&timer));
	return date;
}
 
/*
 *获取时间
 * */
static void settime(){
	time_t timer=time(NULL);
	strftime(loging.logtime,20,"%Y-%m-%d %H:%M:%S",localtime(&timer));
}
 
/*
 *不定参打印
 * */
static void PrintfLog(char * fromat,va_list args){
	int d;
	char c,*s;
	while(*fromat)
	{
		switch(*fromat){
			case 's':{
				s = va_arg(args, char *);
				fprintf(loging.logfile,"%s",s);
				break;}
			case 'd':{
				d = va_arg(args, int);
				fprintf(loging.logfile,"%d",d);
				break;}
			case 'c':{
				c = (char)va_arg(args, int);
				fprintf(loging.logfile,"%c",c);
				break;}
			default:{
				if(*fromat!='%'&&*fromat!='\n')
					fprintf(loging.logfile,"%c",*fromat);
				break;}
		}
		fromat++;
	}
	fprintf(loging.logfile,"%s","\n");
}
 
static int initlog(unsigned char loglevel){
	char strdate[30]={0x0};
	LOGSET *logsetting;
	//获取日志配置信息
	if((logsetting=getlogset())==NULL){
		perror("Get Log Set Fail!");
		return -1;
	}
	if((loglevel&(logsetting->loglevel))!=loglevel)
		return -1;
 
	memset(&loging,0,sizeof(LOG));
	//获取日志时间
	settime();
	if(strlen(logsetting->filepath)==0){
		char *path=getenv("HOME");
		memcpy(logsetting->filepath,path,strlen(path));
 
		getdate(strdate);
		strcat(strdate,".log");
		strcat(logsetting->filepath,"/");
		strcat(logsetting->filepath,strdate);
	}
	memcpy(loging.filepath,logsetting->filepath,MAXFILEPATH);
	//打开日志文件
	if(loging.logfile==NULL)
		loging.logfile=fopen(loging.filepath,"a+");
	if(loging.logfile==NULL){
		perror("Open Log File Fail!");
		return -1;
	}
	//写入日志级别,日志时间
	fprintf(loging.logfile,"[%s] [%s] ",LogLevelText[loglevel-1],loging.logtime);
	return 0;
}
 
/*
 *日志写入
 * */
int LogWrite(unsigned char loglevel,char *fromat,...)
{
	int  rtv = -1;
	va_list args;
	
	//[为支持多线程需要加锁] pthread_mutex_lock(&mutex_log); //lock. 
	
	do{
		//初始化日志
		if(initlog(loglevel) != 0)
		{
			rtv = -1;
			break;
		}
		//打印日志信息
		va_start(args,fromat);
		PrintfLog(fromat,args);
		va_end(args);
		//文件刷出
		fflush(loging.logfile);
		//日志关闭
		if(loging.logfile!=NULL)
			fclose(loging.logfile);
		loging.logfile=NULL;
		rtv = 0;
	}while(0);
	
	//[为支持多线程需要加锁] pthread_mutex_unlock(&mutex_log); //unlock. 
	
	return rtv;
}

test.c 文件:

/** test.c **/
 
#include "stdio.h"
#include "stdlib.h"
#include "log.h"
int main(int argv,char**argc){
	printf("%s\n",argc[0]);
	LogWrite(INFO,"%s","Hello World!");
	LogWrite(DEBUG,"%s","H.e.l.l.o W.o.r.l.d!");
	LogWrite(WARN,"%s","H e l l o W o r l d!");
	LogWrite(ERROR,"%s","Hallo World!");
	return 0;
}
path=./temp
level=ALL

该系统需要在类Unix系统下使用。

下面我演示一下在我的Ubuntu云服务器上的使用方法。

1. 按照上面的要求新建代码文件和目录
root@iZ2zeaqy08amcq9fk440kyZ:/code/log1# tree
.
├── log.c
├── log.conf
├── log.h
├── temp
└── test.c

2. 编译
root@iZ2zeaqy08amcq9fk440kyZ:/code/log1# gcc log.c test.c -o out

3. 运行
root@iZ2zeaqy08amcq9fk440kyZ:/code/log1# ./out 

4. 可打开temp目录下的日志文件看看
root@iZ2zeaqy08amcq9fk440kyZ:/code/log1# tree
.
├── log.c
├── log.conf
├── log.h
├── out
├── temp
│   └── 2021-12-13.log
└── test.c

5. temp目录下的2021-12-13.log文件既是我们写入的日志文件
文件中的内容如下:
[INFO] [2021-12-13 21:18:25] Hello World!
[DEBUG] [2021-12-13 21:18:25] H.e.l.l.o W.o.r.l.d!
[WARN] [2021-12-13 21:18:25] H e l l o W o r l d!
[ERROR] [2021-12-13 21:18:25] Hallo World!
[INFO] [2021-12-13 21:18:59] Hello World!
可以看到记录了日志等级,日期时间,打印信息。

可以看到这个日志系统还是很小巧易用的,适合与在有文件系统的类Unix系统中。

2.C语言日志系统2

下面这个日志系统是打印出来的:C语言日志分级设计

/****************************************************************
***Author: lishuangliang		                      ***
***Email: lishuangliang@outlook.com			      ***
***Date: 2018-09-24					      ***	
***Festivsl: Mid-autumn			          ***								
*****************************************************************/
#include <string.h>  
#include <errno.h>  
#include <stdio.h>  
#include <stdint.h>  
#include <stddef.h>  
#include <stdlib.h>  
#include <sys/stat.h>  
#include <sys/types.h> 
#include <stdarg.h>
#include <stdbool.h>
#include <sys/un.h>
#include <sys/param.h>
#include <time.h> 
 
//通过宏来控制是否打开日志输出
#ifdef DISABLE_DEBUG
#define real_debug_level 0
#else
#define real_debug_level debug_level
#endif
 
 
//定义日志输出级别
#define 	FATALEER    (1<<0)
#define 	ERROR       (1<<1)
#define 	WRAN        (1<<2)
#define 	INFO      (1<<3)
#define 	DEBUG    (1<<4) 
 
#define WriteLog(level,mesg) log_mesg_printf(__FILE__, __LINE__, __func__, level, mesg)
#define WriteLog2(level,format, arg...) log_mesg_printf2( __FILE__,__FUNCTION__, __LINE__, level, format, ##arg)
 
int debug_level = 0;
 
struct dbg {
    int level;
    const char *mesg;
};
 
static struct dbg debug_level_table[] = {
    {FATALEER, "Config The Log Level as FATALEER"},
    {ERROR, "Config The Log Level as ERROR"},
    {WRAN, "Config The Log Level as WRAN"},
    {INFO, "Config The Log Level as INFO"},
    {DEBUG, "Config The Log Level as DEBUG"}
};
 
void print_debug_usage(void)
{
    struct dbg *p;
 
    fprintf(stderr,
            "  To calculate the debug level, logically 'or'\n"
            "  some of the following values together to get a debug level:\n");
    for (p = debug_level_table;
         p <
         debug_level_table +
         (sizeof (debug_level_table) / sizeof (struct dbg)); p++) {
        fprintf(stderr, "\t%d:\t%s\n", p->level, p->mesg);
    }
    fprintf(stderr, "\n");
}
 
void parse_debug(char *foo)
{
    int i;
    struct dbg *p;
 
    if (!foo)
        return;
    fprintf(stderr, "Before parse_debug, debug_level is: %d\n",
            debug_level);
 
    i = atoi(foo);
    if(i == -1) 
	{
		/* error */
		fprintf(stderr, "Invalid level specified.\n");
		exit(0);
    }    
    for (p = debug_level_table;p < debug_level_table +(sizeof (debug_level_table) / sizeof (struct dbg)); p++)
	{
        if (i > 0) {
            if (i & p->level) {                
                fprintf(stderr, "Enabling %s debug level.\n",p->mesg);
                debug_level |= p->level;
            }
        } 
    }
    fprintf(stderr, "After parse_debug, debug_level is: %d\n",
            debug_level);
}
 
char *get_commonlog_time(void)
{
	char *p;
	char sys_time[64];	
	time_t tnow = time(NULL);	
	struct tm *ptm = localtime(&tnow);
	
	memset(sys_time, 0 ,sizeof(sys_time));
	sprintf(sys_time, "%04d-%02d-%02d %02d:%02d:%02d",ptm->tm_year+1900 ,ptm->tm_mon+1 ,ptm->tm_mday ,ptm->tm_hour ,ptm->tm_min ,ptm->tm_sec);
	//return (char *)sys_time;
	p = sys_time;
	return p;
}
 
void log_mesg_printf(const char *file, int line, const char *func,int level, const char *mesg)
{
    if(real_debug_level & level)
	{
		int errno_save = errno;
		fprintf(stderr, "%s%s:%d (%s) - ", get_commonlog_time(), file, line, func);
		errno = errno_save;
		perror(mesg);
		errno = errno_save;
	}	  
}
 
void log_mesg_printf2(const char *file,const char *func,const int line, int level, char *fmt,...)
{
	if(real_debug_level & level)
	{
		char msg_buf[20*1024];
		va_list ap;
		va_start(ap,fmt);
		sprintf(msg_buf,"[%s  %s:%s:%d] ",get_commonlog_time(),file,func,line);
		vsprintf(msg_buf+strlen(msg_buf),fmt,ap);
		fprintf(stderr,"%s\n",msg_buf);		
		va_end(ap);
	}
}
 
int main(int argc, char* argv[])
{
	
#ifdef DISABLE_DEBUG
	print_debug_usage();
	parse_debug(argv[1]);//解析日志打印输出级别
#endif
	//不使用可变参数解析样例
	WriteLog(DEBUG,"I want to DEBUG");
	WriteLog(INFO,"I want to INFO");
	WriteLog(WRAN,"I want to WARN");
	WriteLog(ERROR,"I want to ERROR");
	WriteLog(FATALEER,"I want to FATALEER");
	//使用可变参数解析样例
	WriteLog2(DEBUG,"I want to %s which level is %d","DEBUG",DEBUG);
	WriteLog2(INFO,"I want to %s which level is %d","INFO",INFO);
	WriteLog2(WRAN,"I want to %s which level is %d","WRAN",WRAN);
	WriteLog2(ERROR,"I want to %s which level is %d","ERROR",ERROR);
	WriteLog2(FATALEER,"I want to %s which level is %d","FATALEER",FATALEER);
	
	return 0;
}

3.zlog日志系统

Chapter 1 zlog是什么?

zlog是一个高可靠性、高性能、线程安全、灵活、概念清晰的纯C日志函数库。

事实上,在C的世界里面没有特别好的日志函数库(就像JAVA里面的的log4j,或者C++的log4cxx)。C程序员都喜欢用自己的轮子。printf就是个挺好的轮子,但没办法通过配置改变日志的格式或者输出文件。syslog是个系统级别的轮子,不过速度慢,而且功能比较单调。

所以我写了zlog。

zlog在效率、功能、安全性上大大超过了log4c,并且是用c写成的,具有比较好的通用性。

zlog有这些特性:

  • syslog分类模型,比log4j模型更加直接了当
  • 日志格式定制,类似于log4j的pattern layout
  • 多种输出,包括动态文件、静态文件、stdout、stderr、syslog、用户自定义输出函数
  • 运行时手动、自动刷新配置文件(同时保证安全)
  • 高性能,在我的笔记本上达到25万条日志每秒, 大概是syslog(3)配合rsyslogd的1000倍速度
  • 用户自定义等级
  • 多线程和多进程环境下保证安全转档
  • 精确到微秒
  • 简单调用包装dzlog(一个程序默认只用一个分类)
  • MDC,线程键-值对的表,可以扩展用户自定义的字段
  • 自诊断,可以在运行时输出zlog自己的日志和配置状态
  • 不依赖其他库,只要是个POSIX系统就成(当然还要一个C99兼容的vsnprintf)

相关链接:

主页:http://hardysimpson.github.com/zlog/

下载:https://github.com/HardySimpson/zlog/releases

其实之前我已经写过一篇博客来讲解zlog的简单使用:【C语言开源库】C语言开源库zlog的使用

Chapter 2 zlog不是什么?

zlog的目标是成为一个简而精的日志函数库,不会直接支持网络输出或者写入数据库,不会直接支持日志内容的过滤和解析。

原因很明显,日志库是被应用程序调用的,所有花在日志库上的时间都是应用程序运行时间的一部分,而上面说的这些操作都很费时间,会拖慢应用程序的速度。这些事儿应该在别的进程或者别的机器上做。

如果你需要这些特性,我建议使用rsyslog、zLogFabric、Logstash,这些日志搜集、过滤、存储软件,当然这是单独的进程,不是应用程序的一部分。

目前zlog已经支持7.4,可以自己实现一个输出函数,自由的把日志输出到其他进程或者其他机器。而把日志的分类匹配、日志格式成型的工作交给zlog。

目前我的想法是实现一个zlog-redis客户端,用自定义输出功能,把日志存储到本机或者远程的redis服务器内,然后用其他进程(也使用zlog库)来把日志写到文件里面,不知大家以为这个想法如何?欢迎和我联系探讨。

Chapter 3 Hello World

3.1 编译和安装zlog

下载[zlog-latest-stable.tar.gz](file://https://github.com/HardySimpson/zlog/archive/latest-stable.tar.gz)

$ tar -zxvf zlog-latest-stable.tar.gz
$ cd zlog-latest-stable/

$ make 

$ sudo make install

or

$ sudo make PREFIX=/usr/local/ install

PREFIX指明了安装的路径,安转完之后为了让你的程序能找到zlog动态库

$ sudo vi /etc/ld.so.conf
/usr/local/lib

$ sudo ldconfig

在你的程序运行之前,保证libzlog.so在系统的动态链接库加载器可以找到的目录下。上面的命令适用于linux,别的系统自己想办法。

  • 除了一般的make以外,还可以
$ make 32bit # 32bit version on 64bit machine, libc6-dev-i386 is needed
$ make noopt # without gcc optimization

$ make doc   # lyx and hevea is needed

$ make test  # test code, which is also good example for zlog
  • makefile是用GNU make的格式写的,所以在你的平台上需要预装gnu make和gcc。或者,手工修改一个自己平台的makefile也行。
3.2 应用程序调用和链接zlog

应用程序使用zlog很简单,只要在C文件里面加一行。

#include "zlog.h"

链接zlog需要pthread库,命令是:

$ cc -c -o app.o app.c -I/usr/local/include
  # -I[where zlog.h is put]

$ cc -o app app.o -L/usr/local/lib -lzlog -lpthread

  # -L[where libzlog.so is put]
3.3 Hello World 代码

这些代码在$(top_builddir)/test/test_hello.c, test_hello.conf

  1. 写一个C文件:

    #include <stdio.h> 
    
    #include "zlog.h"
    
     
    
    int main(int argc, char** argv)
    
    {
    
    int rc;
    zlog_category_t *c;
    
    rc = zlog_init("test_hello.conf");
    
    if (rc) {
    
    printf("init failed\n");
    return -1;
    
    }
    
    c = zlog_get_category("my_cat");
    
    if (!c) {
    
    printf("get cat fail\n");
    
    zlog_fini();
    return -2;
    
    }
    
    zlog_info(c, "hello, zlog");
    
    zlog_fini();
    
    return 0;
    
    } 
    
  2. 写一个配置文件,放在和test_hello.c同样的目录下:

    [formats]
    
    simple = "%m%n"
    
    [rules]
    
    my_cat.DEBUG    >stdout; simple
    
  3. 编译、然后运行!

    $ cc -c -o test_hello.o test_hello.c -I/usr/local/include
    $ cc -o test_hello test_hello.o -L/usr/local/lib -lzlog
    
    $ ./test_hello
    
    hello, zlog
    
3.4 更简单的Hello World

这个例子在$(top_builddir)/test/test_default.c, test_default.conf. 源代码是:

#include <stdio.h>
#include "zlog.h"

int main(int argc, char** argv)

{

int rc;
rc = dzlog_init("test_default.conf", "my_cat");

if (rc) {

printf("init failed\n");
return -1;

}

dzlog_info("hello, zlog");

zlog_fini();

return 0;

} 

配置文件是test_default.conf,和test_hello.conf一模一样,最后执行程序的输出也一样。区别在于这里用了dzlog API,内含一个默认的zlog_category_t。详见6.5

Chapter 4 Syslog 模型

4.1 分类(Category)、规则(Rule)和格式(Format)

zlog有3个重要的概念:分类(Category)、规则(Rule)和格式(Format)。

分类(Category)用于区分不同的输入。代码中的分类变量的名字是一个字符串,在一个程序里面可以通过获取不同的分类名的category用来后面输出不同分类的日志,用于不同的目的。

格式(Format)是用来描述输出日志的格式,比如是否有带有时间戳,是否包含文件位置信息等,上面的例子里面的格式simple就是简单的用户输入的信息+换行符。

规则(Rule)则是把分类、级别、输出文件、格式组合起来,决定一条代码中的日志是否输出,输出到哪里,以什么格式输出。

所以,当程序执行下面的语句的时候

zlog_category_t *c;
c = zlog_get_category("my_cat");

zlog_info(c, "hello, zlog");

zlog会找到c的名字是"my_cat",对应的配置文件中的规则是

[rules]
my_cat.DEBUG    >stdout; simple

然后库会检查,目前这条日志的级别是否符合规则中的级别来决定是否输出。因为INFO>=DEBUG,所以这条日志会被输出。并且根据这条规则,会被输出到stdout(标准输出) ,输出的格式是simple,在配置文件中定义是

[formats]
simple = "%m%n" 

最后在屏幕上打印

hello, zlog

这就是整个过程。用户要做就是写自己的信息。日志往哪里输出,以什么格式输出,都是库和配置文件来完成的。

4.2 syslog模型和log4j模型的区别

好,那么目前这个模型和syslog有什么关系呢?至今为止,这个模型还是比较像log4j。log4j的模型里面有logger, appender和layout。区别在于,在log4j里面,代码中的logger和配置中的logger是一一对应的,并且一个logger有唯一的级别。一对一关系是log4j, log4cxx, log4cpp, log4cplus, log4net的唯一选择。

但这种模型是不灵活的,他们发明了过滤器(filters)来弥补,但这只能把事情弄得更加混乱。所以让我们把目光转回syslog的模型,这是一个设计的很简易正确的模型。

继续上一节的例子,如果在zlog的配置文件中有这么2行规则:

[rules]
my_cat.DEBUG     >stdout; simple

my_cat.INFO      >stdout;

然后,一行代码会产生两行输出:

hello, zlog
2012-05-29 10:41:36 INFO [11288:test_hello.c:41] hello, zlog

现在一个代码中的分类对应配置文件中的两条规则。log4j的用户可能会说:"这很好,但是只要在log4j里面放两个appender也能做的一样。"所以继续看下一个例子:

[rules]
my_cat.WARN     "/var/log/aa.log"

my_cat.DEBUG    "/var/log/bb.log"

代码是:

zlog_info(c, "info, zlog");
zlog_debug(c, "debug, zlog");

最后,在aa.log中只有一条日志

2012-05-29 10:41:36 INFO [11288:test_hello.c:41] info, zlog

但在bb.log里面有两条

2012-05-29 10:41:36 INFO [11288:test_hello.c:41] info, zlog
2012-05-29 10:41:36 DEBUG [11288:test_hello.c:42] debug, zlog

从这个例子能看出来区别。log4j无法轻易的做到这一点。在zlog里面,一个分类可以对应多个规则,每个规则有自己的级别、输出和格式。这就让用户能按照需求过滤、多渠道输出自己的所有日志。

Chapter 5 配置文件

大部分的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是等效的。

 类似资料: