目录
一、日志输出功能的重要性
二、日志分级的概念
三、为什么要有日志分级
四、如何设计
1、设置打印级别
2、根据打印级别控制输出范围:
3、测试
4、输出打印信息
(* ̄︶ ̄)创作不易!期待你们的 点赞、收藏和评论喔。
日志输出功能在嵌入式软件开发和调整过程中的重要性,大家应该都清楚,特别是项目出问题之后,却没有日志可以帮忙定位问题,就非常令人痛苦。
因为我们不可能一直通过调试器去单步打点去调试程序,所以嵌入式设备的运行日志显得尤为重要。
所谓日志分级,就是将日志按照不同的层次,有选择的输出。
参考一些高级语言的分级日志设计,我们根据对程序运行信息的类型把控,可以把日志分为5个级别DEBUG、INFO、WARN、ERROR、FATAL。
- DEBUG:主要用于程序开发测试阶段的打印输出,用于验证程序的设计逻辑是否满足上层应用的设计需求,在经过测试检验后的发布程序可以把它关掉。
- INFO:这个级别的打印输出是用来告诉测试人员或者开发人员一些提示的信息。
- WRAN:这是一种警告的打印输出,它一般是用来输出诸如用户输入错误的数据之类的警告打印,这个级别的打印输出在程序发布后也建议保留,以方面后期程序的维护追踪。
- ERROR:运行出错的打印,这个级别的打印在发布的软件不可关闭,否则无法从发布软件中获取一些反馈信息来指导我们新的程序优化设计。
- FATAL :程序运行遇到这种级别的问题,很难修复,一般伴随着程序的闪退或重启,此时FATAL ERROR的打印则非常关键了,可以帮助我们定位到程序跑飞的原因,FATAL ERROR级别的打印在任何时刻都不可以关闭。
一个好的日志分级设计,有助于我们快速定位问题,主要是方便后续开发和维护。
在设计软件的时候,可以根据问题出现的轻重缓急,有决策的去添加分层信息,在必要的时候有针对性的打开和关闭一些日志。
目前有两种粗浅的设计策略:一种是或的关系,即各个日志等级彼此独立,可以单独打开关闭;一种是顺序打印,根据设置打印等级,低于或者高于这个等级的才打印。
两种没有孰好孰坏,根据需要选择合适策略即可。
/* module_debug.h */
/*1. 设置打印级别 */
enum {
LOG_LEVEL_NONE,
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARN,
LOG_LEVEL_ERROR,
LOG_LEVEL_FATAL,
};
/* 2. log 打印 重写 */
void log_fun(int level, const char *opt, const char* tag, int line, const char *func, const char *fmt, ...);
/* 3. 各打印级别宏 */
/*
*@LOG_DBG
*/
#define LOG_DBG(tag, fmt, ...) \
log_fun(LOG_LEVEL_DEBUG, "D", tag , __LINE__, __func__, fmt, ##__VA_ARGS__)
/*
*@LOG_INFO
*/
#define LOG_INFO(tag, fmt, ...) \
log_fun(LOG_LEVEL_INFO, "I", tag , __LINE__, __func__, fmt, ##__VA_ARGS__)
/*
*@LOG_WARN
*/
#define LOG_WARN(tag, fmt, ...) \
log_fun(LOG_LEVEL_WARN, "W", tag , __LINE__, __func__, fmt, ##__VA_ARGS__)
/*
*@LOG_ERR
*/
#define LOG_ERR(tag, fmt, ...) \
log_fun(LOG_LEVEL_ERROR, "E", tag , __LINE__, __func__, fmt, ##__VA_ARGS__)
/*
*@LOG_FATAL
*/
#define LOG_FATAL(tag, fmt, ...) \
log_fun(LOG_LEVEL_FATAL, "F", tag , __LINE__, __func__, fmt, ##__VA_ARGS__)
注:
... 和 __VA_ARGS__.省略点表示可变参数,__VA_ARGS__表示可变参数的宏,是C99规范中新增的,用来替换宏定义中的可变参数(...); ##运算符将两个宏参数连接在一起。##__VA_ARGS__ 这里主要是为了解决当__VA_ARGS__为空时编译问题,使用##防止编译出错。
/* module_debug.c */
#include
#include
#include
#include
#include
#include
#include
int g_current_dbg_level = LOG_LEVEL_DEBUG;
void log_fun(int level, const char *opt, const char* tag, int line, const char *func, const char *fmt, ...)
{
if (level > g_current_dbg_level) {
char msg_buf[20*1024];
va_list ap;
va_start(ap,fmt);
sprintf(msg_buf,"%s/%s (%d): %s() ",opt, tag, line, func);
vsprintf(msg_buf+strlen(msg_buf),fmt,ap);
fprintf(stderr,"%s\n",msg_buf); /* 输出到标准输出 */
va_end(ap);
}
}
/* 设置 打印级别 */
void ModuleDebugInit(int level)
{
g_current_dbg_level = level;
}
/* main.c */
#include
#define TAG "test"
int main (int argc , char *argv[])
{
LOG_DBG(TAG, "log_debug %d \n", LOG_LEVEL_DEBUG);
LOG_INFO(TAG, "log_info %d \n", LOG_LEVEL_INFO);
LOG_WARN(TAG, "log_warn \n");
LOG_ERR(TAG, "log_err \n");
return 0;
}
I/test (1): main() log_info 2
W/test (2): main() log_warn
E/test (3): main() log_err
其中,I表示INFO、W表示WARN、E表示ERROR;紧接着跟着模块(test),也可以是文件名;然后是行号、函数名,最后是打印信息。
当然,具体打印信息和风格用户可以根据需要,自行设计。