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

mculover666 发表于 2020/02/03 20:26:44 2020/02/03
【摘要】 1. 做实验引发的思考在学习LiteOS日志打印组件使用的时候,我记录了一篇博客:atiny_log | LiteOS 物联网操作系统中的日志打印组件使用分享,关于实验的具体内容,请阅读这篇博客。在实验时我编写了如下的5行代码:ATINY_LOG(LOG_DEBUG, "This is a LOG_DEBUG Test!\r\n");ATINY_LOG(LOG_INFO, "This is ...

1. 做实验引发的思考

image.png

在学习LiteOS日志打印组件使用的时候,我记录了一篇博客:atiny_log | LiteOS 物联网操作系统中的日志打印组件使用分享,关于实验的具体内容,请阅读这篇博客。

在实验时我编写了如下的5行代码:

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");

串口的输出如下:

image.png

在串口输出的信息中:

① 第一个方括号是该条日志的输出等级:可以用宏定义选择Debug、INFO、WARNING、ERR、FATAL五个等级中的一个;

② 第二个方括号是RTOS在打印信息时的tick值,可以理解为系统当前的时间戳;

③ 最后一个方括号是指定的打印内容;

可让我感到非常疑惑不解的是:

第三个方括号中竟然打印的是该条打印语句所在的函数名称和所在文件中的位置(行数),并且打印出的行号和实际对应,这个在实际应用中用来定位问题非常方便:
image.png
经过一番查看源码,我终于探索出程序为什么可以知道并且打印出代码所在位置的~

2. 揭晓谜底

其实,这些RTOS系统之所以准确的打印出了代码所在函数及所在位置,不是用于了多么复杂高深的技术,同样也只是在代码里巧妙的利用了C语言的一个不常用知识点 —— 编译器内置宏定义

C语言编译器中内置了一些宏定义,这些内置宏定义可以巧妙地帮我们输出非常有用的调试信息,在RTOS的日志打印组件中通常用到了这三个内置宏定义:

  • __FILE__:在源文件中**当前源文件名;

  • __FUNCTION__: 在源文件中**当前函数名;

  • __LINE__:在源代码中**当前源代码行号;

利用这三个宏定义,使用一行代码即可编写一个最简单的日志打印组件

#define DEBUG(format,...) printf("[%s:%05d][%s]"format"\r\n", __FILE__, __LINE__, __FUNCTION__)

编写一个小程序测试这个仅有一行代码的日志打印组件:

#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;}

编译运行:
image.png

怎么样?所有的信息是不是非常准确?这个仅有一行代码的日志打印组件用起来是不是很爽?

3. RTOS中的完整日志打印组件

当然,一个完整的日志打印组件不能仅仅靠这一行代码来实现,还需要添加很多功能,比如:

  • 设置日志输出等级,区分不同的日志输出;

  • 底层使用自己优化后的printf函数;

  • 添加宏定义控制输出信息是否启用;

  • 添加字符输出颜色控制功能,可与日志输出等级对应;

  • 更多功能,等你探索……

在LiteOS中,日志打印组件底层使用了该内置宏定义功能,源码如下:

#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中有这样的一个功能源码:

/* 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中也有同样的断言功能源码:

#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』。

image.png

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。