C语⾔⽇志分级设计
前⾔
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, ) \
log_mesg_printf2( __FILE__,__FUNCTION__, __LINE__, level, format, ##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: lishuangliang@outlook        ***
***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, ) 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);
WriteLog2(FATALEER,"I want to %s which level is %d","FATALEER",FATALEER);
return 0;
}
linux下样例运⾏的效果
总结
⽇志功能设计的好坏影响整个程序⼯程的开发效率和后期的运维,分级⽇志设计则能够很好解决此问题。通过分级⽇志的分析,可以快速定位问题、优化维护程序。中秋佳节,总结整理,转载说明出处。
参考⽂献