[技术干货] 一行代码就能写一个日志打印组件,你信吗?为你揭晓LiteOS中日志打印组件的核心

![](https://img-blog.csdnimg.cn/20200202094937735.png#pic_center) # 1. 做实验引发的思考 在学习LiteOS日志打印组件使用的时候,我记录了一篇博客:[atiny_log | LiteOS 物联网操作系统中的日志打印组件使用分享](https://blog.csdn.net/Mculover666/article/details/104076964),关于实验的具体内容,请阅读这篇博客。 在实验时我编写了如下的5行代码: ```c ATINY_LOG(LOG_DEBUG, "This is a LOG_DEBUG Test!\r\n"); ATINY_LOG(LOG_INFO, "This is a LOG_INFO Test!\r\n"); ATINY_LOG(LOG_WARNING, "This is a LOG_WARNING Test!\r\n"); ATINY_LOG(LOG_ERR,"This is a LOG_ERR Test!\r\n"); ATINY_LOG(LOG_FATAL, "This is a LOG_FATAL Test!\r\n"); ``` 串口的输出如下: ![](https://img-blog.csdnimg.cn/20200203081415169.png) 在串口输出的信息中: ① 第一个方括号是该条日志的输出等级:可以用宏定义选择Debug、INFO、WARNING、ERR、FATAL五个等级中的一个; ② 第二个方括号是RTOS在打印信息时的tick值,可以理解为系统当前的时间戳; ③ 最后一个方括号是指定的打印内容; 可让我感到非常疑惑不解的是: 第三个方括号中竟然打印的是**该条打印语句所在的函数名称和所在文件中的位置(行数),并且打印出的行号和实际对应**,这个在实际应用中用来定位问题非常方便: ![](https://img-blog.csdnimg.cn/2020020308204640.png) 经过一番查看源码,我终于探索出程序为什么可以知道并且打印出代码所在位置的~ # 2. 揭晓谜底 其实,这些RTOS系统之所以准确的打印出了代码所在函数及所在位置,不是用于了多么复杂高深的技术,同样也只是在代码里巧妙的利用了C语言的一个不常用知识点 —— **编译器内置宏定义**。 C语言编译器中内置了一些宏定义,这些内置宏定义可以巧妙地帮我们输出非常有用的调试信息,在RTOS的日志打印组件中通常用到了这三个内置宏定义: - `__FILE__`:在源文件中**当前源文件名; - `__FUNCTION__`: 在源文件中**当前函数名; - `__LINE__`:在源代码中**当前源代码行号; 利用这三个宏定义,**使用一行代码即可编写一个最简单的日志打印组件**: ```c #define DEBUG(format,...) printf("[%s:%05d][%s]"format"\r\n", __FILE__, __LINE__, __FUNCTION__) ``` 编写一个小程序测试这个仅有一行代码的日志打印组件: ```c #include <stdio.h> #define DEBUG(format,...) printf("[%s:%05d][%s]"format"\r\n", __FILE__, __LINE__, __FUNCTION__) int main(void) { DEBUG("Hello, esay log tools."); return 0; } ``` 编译运行: ![](https://img-blog.csdnimg.cn/20200203084255980.png) 怎么样?所有的信息是不是非常准确?这个仅有一行代码的日志打印组件用起来是不是很爽? # 3. RTOS中的完整日志打印组件 当然,一个完整的日志打印组件不能仅仅靠这一行代码来实现,还需要添加很多功能,比如: - 设置日志输出等级,区分不同的日志输出; - 底层使用自己优化后的printf函数; - 添加宏定义控制输出信息是否启用; - 添加字符输出颜色控制功能,可与日志输出等级对应; - 更多功能,等你探索…… 在LiteOS中,日志打印组件底层使用了该内置宏定义功能,源码如下: ```c #define ATINY_LOG(level, fmt, ...) \ do \ { \ if ((level) >= atiny_get_log_level()) \ { \ (void)atiny_printf("[%s][%u][%s:%d] " fmt "\r\n", \ atiny_get_log_level_name((level)), (uint32_t)osal_sys_time(), __FUNCTION__, __LINE__, ##__VA_ARGS__); \ } \ } while (0) #else #define ATINY_LOG(level, fmt, ...) #endif ``` 而在更多时候,**该功能被用来迅速的输出系统断言信息**,比如在RT-Thread中有这样的一个功能源码: ```c /* assert for developer. */ #ifdef ULOG_ASSERT_ENABLE #define ULOG_ASSERT(EXPR) \ if (!(EXPR)) \ { \ ulog_output(LOG_LVL_ASSERT, LOG_TAG, RT_TRUE, "(%s) has assert failed at %s:%ld.", #EXPR, __FUNCTION__, __LINE__); \ ulog_flush(); \ while (1); \ } #else #define ULOG_ASSERT(EXPR) #endif ``` 在TencentOS tiny中也有同样的断言功能源码: ```c #define TOS_ASSERT_AUX(exp, function, line) \ if (!(exp)) { \ tos_kprintln("assert failed: %s %d\n", function, line); \ tos_knl_sched_lock(); \ tos_cpu_int_disable(); \ while (K_TRUE) { \ ; \ } \ } #define TOS_ASSERT(exp) TOS_ASSERT_AUX(exp, __FUNCTION__, __LINE__) ``` **接收更多精彩文章及资源推送,欢迎订阅我的微信公众号:『mculover666』。** ![](https://img-blog.csdnimg.cn/20200202092055136.png)