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");
串口的輸出如下:
在串口輸出的信息中:
① 第一個方括號是該條日誌的輸出等級:可以用宏定義選擇Debug、INFO、WARNING、ERR、FATAL五個等級中的一個;
② 第二個方括號是RTOS在打印信息時的tick值,可以理解爲系統當前的時間戳;
③ 最後一個方括號是指定的打印內容;
可讓我感到非常疑惑不解的是:
第三個方括號中竟然打印的是該條打印語句所在的函數名稱和所在文件中的位置(行數),並且打印出的行號和實際對應,這個在實際應用中用來定位問題非常方便:
經過一番查看源碼,我終於探索出程序爲什麼可以知道並且打印出代碼所在位置的~
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;}
編譯運行:
怎麼樣?所有的信息是不是非常準確?這個僅有一行代碼的日誌打印組件用起來是不是很爽?
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