前言

C语言是一门面向过程的编程语言,其程序的设计流程就是根据上层的业务需求编写一系列函数,中间辅以if、for、while、switch等流程控制语句来实现各种数据的处理。从程序可靠运行方面考虑,我们希望程序的执行过程可以被程序员可控了解,这在程序开发过程中以及程序的后期维护都至关重要。能实现这种功能的最好方法就是在程序运行的关键节点添加日志打印追踪了。一个良好的日志打印输出设计可以使程序员无论是在程序开发还是后期的维护都能够快速定位到程序运行出错的问题,提升程序员处理bug的能力。一般在开法测试阶段,我们希望能够尽可能输出程序运行的关键信息,以帮助修复完善软件开发初级时的bug,但是后期软件发布后,我们又希望只保留尽可能少的关键打印输出,以提高程序运行效率。对于这种矛盾的需求我们可以使用分级的日志打印输出来实现。

日志分级概念

参考一些诸如Java等高级语言的分级日志设计,我们根据对程序运行信息的类型把控,可以把日志分为5个级别DEBUG、INFO、WARN、ERROR、FATAL ERROR。

DEBUG:主要用于程序开发测试阶段的打印输出,用于验证程序的设计逻辑是否满足上层应用的设计需求,在经过测试检验后的发布程序可以把它关掉。

INFO:这个级别的打印输出是用来告诉测试人员或者开发人员一些特殊的信息,比如一些关键数据的操作。

WRAN:这是一种警告的打印输出,它一般是用来输出诸如用户输入错误的数据之类的警告打印,这个级别的打印输出在程序发布后也建议保留,以方面后期程序的维护追踪。

ERROR:运行出错的打印,优秀的程序设计都具有很好的容错性能,在程序运行出错的时候能够自己修正回来,举个例子,在网络编程中遇到数据请求错误,我们可以启用重连机制的方法去试图解决程序运行出错的问题。这个级别的打印在发布的软件不可关闭,否则无法从发布软件中获取一些反馈信息来指导我们新的程序优化设计。

FATAL ERROR:程序运行遇到这种级别的问题,很难修复,一般伴随着程序的闪退或重启,此时FATAL ERROR的打印则非常关键了,可以帮助我们定位到程序跑飞的原因,FATAL ERROR级别的打印在任何时刻都不可以关闭。

日志分级设计

在C语言中,分级日志的输出控制实现可以借助强大的宏来做;日志输出分级的配置则可以利用程序的运行参数来指定或者使用配置文件等其他手段来实现;对输出内容的格式规范控制则一般在每行开头加上实时时间、程序运行的文件、函数、行数等信息,以实现快速查询定位;日志内容输出的位置可以是串口打印或者log信息文件,推荐使用log文件保存。

C语言中常用的 #、##和__VA_ARGS__  等宏简介

1.#

假如希望在字符串中包含宏参数,ANSI C允许这样作,在类函数宏的替换部分,#符号用作一个预处理运算符,它可以把语言符号转化程字符串。例如,如果x是一个宏参量,那么#x可以把参数名转化成相应的字符串。该过程称为字符串化(stringizing)。

//x的平方可以这样实现
 #incldue <stdio.h>
 #define PSQR(x) printf("the square of" #x "is %d.\n",(x)*(x))

2.##

 ##运算符可以用于类函数宏的替换部分。另外,##还可以用于类对象宏的替换部分。这个运算符把两个语言符号组合成单个语言符号。例如:

#include <stdio.h>
#define XNAME(n) x##n
#define PXN(n) printf("x"#n" = %d\n",x##n)
#define WriteLog2(level,format, arg...) \
log_mesg_printf2( __FILE__,__FUNCTION__, __LINE__, level, format, ##arg)// 当arg...是空参数时,##运算符把它前面的,号“吃”掉了,以解决编译问题。

int main(void)
 {
     int XNAME(1)=12;//int x1=12;
     PXN(1);//printf("x1 = %d\n", x1);
     return 0;
 }

3.可变参数宏 ...和_ _VA_ARGS_ _

 __VA_ARGS__ 是一个可变参数的宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏_ _VA_ARGS_ _就可以被用在替换部分中,替换省略号所代表的字符串。比如:

#define PR(...) printf(__VA_ARGS__)
 int main()
 {
     int wt=1,sp=2;
     PR("hello\n");
     PR("weight = %d, shipping = %d",wt,sp);
     return 0;
 }

实现样例

样例很简单,通过利用日志分级的思想和可变参数宏来实现功能,整个样例代码必要的时候可以通过编译选项来控制是否需要编译日志输出,以缩小代码;此外在调测阶段,则可以利用配置参数来实现不同等级的日志打印输出。

/****************************************************************
***Author: lishuangliang		                      ***
***Email: [email protected]			      ***
***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;
}

linux下样例运行的效果

C语言日志分级设计

 总结

日志分级源代码已经托管码云,编译运行没有任何warning,点击此下载。日志功能设计的好坏影响整个程序工程的开发效率和后期的运维,分级日志设计则能够很好解决此问题。通过分级日志的分析,可以快速定位问题、优化维护程序。中秋佳节,总结整理,转载说明出处。

参考文献

https://www.cnblogs.com/yulinfeng/archive/2017/10/18/7689388.html

https://blog.csdn.net/bat67/article/details/77542165

https://yq.aliyun.com/articles/399064

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-06-27
  • 2021-06-17
  • 2022-02-06
  • 2021-08-22
  • 2022-12-23
猜你喜欢
  • 2022-02-18
  • 2022-12-23
  • 2021-07-18
  • 2022-01-15
  • 2022-01-03
  • 2021-07-17
  • 2022-12-23
相关资源
相似解决方案