1. 做实验引发的思考

在学习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

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐