RT-Thread ulog 日誌組件應用筆記 - 基礎篇

 摘要前端


本應用筆記介紹了 RT-Thread ulog 組件的基本知識和 ulog 的基本使用方法,幫助開發者更好地上手、入門 RT-Thread ulog 組件。更多 ulog 組件的高級用法,詳見《RT-Thread ulog 日誌組件應用筆記 - 進階篇》。
linux

一、本文的目的和結構

本文的目的和背景

日誌的定義日誌是將軟件運行的狀態、過程等信息,輸出到不一樣的介質中(例如:文件、控制檯、顯示屏等),並進行顯示和保存。爲軟件調試、維護過程當中的問題追溯、性能分析、系統監控、故障預警等功能,提供參考依據。能夠說,日誌的使用,幾乎佔用的軟件生命週期的至少 80% 的時間。web

日誌的重要性對於操做系統而言,因爲其軟件的複雜度很是大,單步調試在一些場景下並不適合,因此日誌組件在操做系統上幾乎都是標配。完善的日誌系統也能讓操做系統的調試事半功倍。express

ulog 的起源 : RT-Thread 一直缺乏小巧、實用的日誌組件,而 ulog 的誕生補全了這塊的短板。它將做爲 RT-Thread 的基礎組件被開源出來,讓咱們的開發者也能用上簡潔易用的日誌系統,提升開發效率。後端

本文的結構

本應用筆記將從如下幾個方面來介紹 RT-Thread ulog 組件:數組

  • ulog 組件簡介、框架總覽緩存

  • ulog 組件的配置安全

  • ulog 組件基本功能的使用服務器

二、問題闡述

本應用筆記將圍繞下面幾個問題來介紹 RT-Thread ulog 組件。微信

  • ulog 組件的主要功能有哪些?

  • 經常使用的日誌接口有哪些?

  • 如何使用 ulog?

想要解決這些問題,首先須要認識 RT-Thread ulog 組件基本功能,而後熟悉經常使用的日誌 API,最後將在 qemu 平臺上演示 ulog 的使用方法。

三、問題的解決

ulog 簡介

ulog 是一個很是簡潔、易用的 C/C++ 日誌組件,第一個字母 u 表明 μ,即微型的意思。它能作到最低 ROM<1K, RAM<0.2K 的資源佔用。ulog 不只有小巧體積,一樣也有很是全面的功能,其設計理念參考的是另一款 C/C++ 開源日誌庫:EasyLogger(簡稱 elog),並在功能和性能等方面作了很是多的改進。主要特性以下:

  • 日誌輸出的 後端多樣化 ,可支持例如:串口、網絡,文件、閃存等後端形式;

  • 日誌輸出被設計爲 線程安全 的方式,並支持 異步輸出 模式;

  • 日誌系統 高可靠,在中斷 ISR 、Hardfault 等複雜環境下依舊可用;

  • 支持 運行期/編譯期 開關控制全局的日誌輸出級別;

  • 各模塊的日誌支持 運行期/編譯期 設置輸出級別;

  • 日誌內容支持按 關鍵詞及標籤 方式進行全局過濾;

  • API 和日誌格式可兼容 linux syslog

  • 支持以 hex 格式 dump 調試數據到日誌中;

  • 兼容 rtdbg (RTT 早期的日誌頭文件)及 EasyLogger 的日誌輸出 API。

ulog 框架總覽

ulog 框架

上圖爲 ulog 的內部框架圖,因而可知:

  • 前端 :該層做爲離應用最近的一層,給用戶提供了 syslog 及 LOG_X 兩類 API 接口,方便用戶在不一樣的場景中使用;

  • 核心 :中間核心層的主要工做是將上層傳遞過來的日誌,按照不一樣的配置要求進行格式化與過濾而後生成日誌幀,最終經過不一樣的輸出模塊,輸出到最底層的後端設備上;

  • 後端 :接收到核心層發來的日誌幀後,將日誌輸出到已經註冊的日誌後端設備上,例如:文件、控制檯、日誌服務器等等。

配置說明

下載 RT-Thread 源碼,使用 env 工具進入 rt-thread\bsp\qemu-vexpress-a9 文件夾,輸入 menuconfig 打開配置菜單,在 RT-Thread ComponentsUtilities 下能夠看到 ulog 的配置項,將其使能能夠看到以下配置界面:

ulog 配置

每一個選項配置說明以下:

  • The static output log level. (Debug) :選擇靜態的日誌輸出級別。選擇完成後,比設定級別低的日誌(這裏特指使用 LOG_X API 的日誌)將不會被編譯到 ROM 中。

  • Enable ISR log :使能中斷 ISR日誌,即在 ISR 中也可使用日誌輸出 API 。

  • Enable assert check :使能斷言檢查。關閉後,斷言的日誌將不會被編譯到 ROM 中。

  • The log's max width :日誌的最大長度。因爲 ulog 的日誌 API 按行做爲單位,因此這個長度也表明一行日誌的最大長度。

  • Enable async output mode :使能異步日誌輸出模式。開啓這個模式後,日誌不會馬上輸出到後端,而是先緩存起來,而後交給日誌輸出線程(例如:idle 線程)去輸出。該模式的好處有不少,將在 《RT-Thread ulog 日誌組件應用筆記 - 進階篇》 中詳細介紹。

  • log format :配置日誌的格式,例如:時間信息,顏色信息,線程信息,是否支持浮點等等。

  • Enable console backend :使能控制檯做爲後端。使能後日志能夠輸出到控制檯串口上。建議保持開啓。

  • Enable runtime log filter:使能運行時的日誌過濾器,即動態過濾。使能後,日誌將支持按標籤、關鍵詞等方式,在系統運行時進行動態過濾。

  • Enable syslog format log and API :使能 linux syslog API 及對應的日誌格式。

使用默認配置便可,保存並退出 menuconfig 。

日誌標籤

標籤(tag)是一種常見的分類方式,ulog 也給每條 log 賦予了標籤的屬性,便於分類管理。

使用標籤保證日誌模塊化

因爲日誌輸出量的不斷增大,爲了不日誌被雜亂無章的輸出出來,就須要使用標籤給每條日誌進行分類。標籤的定義是按照 模塊化 的方式,例如:Wi-Fi 組件包括設備驅動(wifi_driver)、設備管理(wifi_mgnt)等模塊,則 Wi-Fi 組件內部模塊可使用 wifi.driverwifi.mgnt 等做爲標籤,進行日誌的分類輸出。

每條日誌的標籤屬性也能夠被輸出並顯示出來,同時 ulog 還能夠設置每一個標籤(模塊)對應日誌的輸出級別,當前不重要模塊的日誌能夠選擇性關閉,不只下降 ROM 資源,還能幫助開發者過濾無關日誌。

各個標籤(模塊)對應日誌的輸出級別也支持在運行時動態調整,詳見《RT-Thread ulog 日誌組件應用筆記 - 進階篇》。

標籤的定義方法

參見 rt-thread\examples\ulog_example.c ulog 例程文件,在文件頂部有定義 LOG_TAG 宏:

1#define LOG_TAG              "example"     //該模塊對應的標籤。不定義時,默認:NO_TAG
2#define LOG_LVL              LOG_LVL_DBG   //該模塊對應的日誌輸出級別。不定義時,默認:調試級別
3#include <ulog.h>                          //必須在 LOG_TAG 與 LOG_LVL 下面

須要注意的,定義日誌標籤時,必須 位於 #include <ulog.h> 的上方,不然會使用默認的 NO_TAG(不推薦定義在頭文件中定義這些宏)。

日誌標籤的做用域是當前源碼文件,項目源代碼一般也會按照模塊進行文件分類。因此在定義標籤時,能夠指定模塊名、子模塊名做爲標籤名稱,這樣不只在日誌輸出顯示時清晰直觀,也能方便後續按標籤方式動態調整級別或過濾。

日誌級別

日誌級別表明了日誌的重要性,在 ulog 中 由高到低 ,有以下幾個日誌級別

級別 名稱 描述
LOG_LVL_ASSERT 斷言 發生沒法處理、致命性的的錯誤,以致於系統沒法繼續運行的斷言日誌
LOG_LVL_ERROR 錯誤 發生嚴重的、不可修復 的錯誤時輸出的日誌屬於錯誤級別日誌
LOG_LVL_WARNING 警告 出現一些不過重要的、具備 可修復性 的錯誤時,會輸出這些警告日誌
LOG_LVL_INFO 信息 給本模塊上層使用人員查看的重要提示信息日誌,例如:初始化成功,當前工做狀態等。該級別日誌通常在量產時依舊 保留
LOG_LVL_DBG 調試 給本模塊開發人員查看的調試日誌,該級別日誌通常在量產時 關閉

設定級別的分類

在 ulog 中,可分爲以下幾類日誌級別

  • 靜態級別與動態級別:是按照日誌是否能夠在 運行階段修改 進行分類,可在運行階段修改的稱之爲動態級別,只能在 編譯階段 修改的稱之爲靜態級別。比靜態級別低的日誌(這裏特指使用 LOG_X API 的日誌)將不會被編譯到 ROM 中,最終也不會輸出、顯示出來。而動態級別能夠管控的是高於或等於靜態級別的日誌。在 ulog 運行時,比動態級別低的日誌會被過濾掉。

  • 全局級別與模塊級別:是按照 做用域 進行的分類。在 ulog 中每一個文件(模塊)也能夠設定獨立的日誌級別。全局級別做用域大於模塊級別,也就是模塊級別只能管控那些高於或等於全局級別的模塊日誌。

綜合上面分類能夠看出,在 ulog 能夠經過如下 4 個方面來設定日誌的輸出級別

  • 全局靜態 日誌級別:在 menuconfig 中配置,對應 ULOG_OUTPUT_LVL

  • 全局動態 日誌級別:使用 void ulog_global_filter_lvl_set(rt_uint32_t level) 函數來設定

  • 模塊靜態 日誌級別:在模塊(文件)內定義 LOG_LVL 宏,與日誌標籤宏 LOG_TAG 定義方式相似

  • 模塊動態 日誌級別:使用 int ulog_tag_lvl_filter_set(const char *tag, rt_uint32_t level) 函數來設定

它們的做用範圍關係以下:

全局靜態 > 全局動態 > 模塊靜態 > 模塊動態

日誌輸出 API 的使用

ulog 主要有兩種日誌輸出 API

  • LOG_X("msg") 宏 API :X 對應的是不一樣級別的第一個字母大寫,API 爲 LOG_DLOG_E 等。這種方式是首選,一方面由於其 API 格式簡單,入參只有一個即日誌信息,再者還支持按模塊靜態日誌級別過濾。

  • ulog_x("tag", "msg")宏 API:x 對應的是不一樣級別的簡寫,這個 API 適用於在一個文件中使用不一樣 tag 輸出日誌的狀況。

日誌輸出 API 的使用方法

下面將以 ulog 例程進行介紹,打開 rt-thread\examples\ulog_example.c 能夠看到,頂部有定義該文件的標籤及靜態優先級

1#define LOG_TAG              "example"
2#define LOG_LVL              LOG_LVL_DBG
3#include <ulog.h>

void ulog_example(void) 函數中有使用 LOG_X API ,大體以下:

1/* output different level log by LOG_X API */
2LOG_D("LOG_D(%d): RT-Thread is an open source IoT operating system from China.", count);
3LOG_I("LOG_I(%d): RT-Thread is an open source IoT operating system from China.", count);
4LOG_W("LOG_W(%d): RT-Thread is an open source IoT operating system from China.", count);
5LOG_E("LOG_E(%d): RT-Thread is an open source IoT operating system from China.", count);

這些日誌輸出 API 均支持 printf 格式,而且會在日誌末尾自動換行。

下面將在 qemu 上展現下 ulog 例程的運行效果:

  • rt-thread\examples\ulog_example.c 拷貝至 rt-thread\bsp\qemu-vexpress-a9\applications 文件夾下

  • 在 env 中進入 rt-thread\bsp\qemu-vexpress-a9 目錄

  • 肯定以前已執行過 ulog 的配置後,執行 scons 命令並等待編譯完成

  • 運行 qemu.bat 來打開 RT-Thread 的 qemu 模擬器

  • 輸入 ulog_example 命令,便可看到 ulog 例程運行結果,大體效果以下圖

ulog 例程

能夠看到每條日誌都是按行顯示,不一樣級別日誌也有着不一樣的顏色。在日誌最前面有當前系統的 tick ,中間有顯示日誌級別及標籤,最後面是具體的日誌內容。在本文後面也會重點介紹這些日誌格式及配置說明。

輸出 raw 日誌

LOG_Xulog_x 這類 API 輸出都是帶格式日誌,有些時候須要輸出不帶任何格式的日誌時,可使用LOG_RAW  或 void ulog_raw(const char *format, ...) 函數。例如:

1LOG_RAW("\r");
2ulog_raw("\033[2A");

在中斷 ISR 中使用

不少時候須要在中斷 ISR 中輸出日誌,可是中斷 ISR 可能會打斷正在進行日誌輸出的線程。要保證中斷日誌與線程日誌互不干涉,就得針對於中斷狀況進行特殊處理。

ulog 已集成中斷日誌的功能,可是默認沒有開啓,使用時打開 Enable ISR log 選項便可,日誌的 API 與線程中使用的方式一致,例如:

 1#define LOG_TAG              "driver.timer"
2#define LOG_LVL              LOG_LVL_DBG
3#include <ulog.h>
4
5void Timer2_Handler(void)
6
{
7    /* enter interrupt */
8    rt_interrupt_enter();
9
10    LOG_D("I'm in timer2 ISR");
11
12    /* leave interrupt */
13    rt_interrupt_leave();
14}

這裏說明下中斷日誌在 ulog 處於同步模式與異步模式下的不一樣策略:

  • 同步模式下 :若是線程此時正在輸出日誌時來了中斷,此時若是中斷裏也有日誌要輸出,會直接輸出到控制檯上,不支持輸出到其餘後端;

  • 異步模式下 :若是發生上面的狀況,中斷裏的日誌會先放入緩衝區中,最終和線程日誌一塊兒交給日誌輸出線程來處理。

斷言

ulog 也提供裏斷言 API :ASSERT(表達式) ,當斷言觸發時,系統會中止運行,內部也會執行 ulog_flush() ,全部日誌後端將執行 flush 。若是開啓了異步模式,緩衝區中全部的日誌也將被 flush 。斷言的使用示例以下:

1void show_string(const char *str)
2
{
3    ASSERT(str);
4    ...
5}

設置日誌格式

ulog 支持的日誌格式能夠在 menuconfig 中配置,位於RT-Thread ComponentsUtilities →  ulog →  log format,具體配置以下:

ulog 格式配置

分別能夠配置:浮點型數字的支持(傳統的 rtdbg/rt_kprintf 均不支持浮點很多天志)、帶顏色的日誌、時間信息(包括時間戳)、級別信息、標籤信息、線程信息。下面咱們將這些選項 **所有選中 **,保存後從新編譯並在 qemu 中再次運行 ulog 例程,看下實際的效果:

ulog 例程(所有格式)

能夠看出,相比第一次運行例程,時間信息已經由系統的 tick 數值變爲時間戳信息,而且線程信息也已被輸出出來。

hexdump

hexdump 也是日誌輸出時較爲經常使用的功能,經過 hexdump 能夠將一段數據以 hex 格式輸出出來,對應的 API 爲:void ulog_hexdump(const char *tag, rt_size_t width, rt_uint8_t *buf, rt_size_t size) ,下面看下具體的使用方法及運行效果:

1/* 定義一個 128 個字節長度的數組 */
2uint8_t i, buf[128];
3/* 在數組內填充上數字 */
4for (i = 0; i < sizeof(buf); i++)
5{
6    buf[i] = i;
7}
8/* 以 hex 格式 dump 數組內的數據,寬度爲 16 */
9ulog_hexdump("buf_dump_test"16, buf, sizeof(buf));

能夠將上面的代碼拷貝到 ulog 例程中運行,而後再看下實際運行結果:

ulog 例程(hexdump)

能夠看出,中部爲  buf 數據的 16 進制 hex 信息,最右側爲各個數據對應的字符信息。

至此,關於 ulog 的入門基礎已經介紹完了,想要了解更多關於 ulog 的進階使用,能夠繼續查看 《RT-Thread ulog 日誌組件應用筆記 - 進階篇》

四、常見問題

  • 一、日誌代碼已執行,可是無輸出

    參考 日誌級別 章節,瞭解日誌級別分類,並檢查日誌過濾參數。還有種多是不當心將控制檯後端給關閉了,從新開啓 Enable console backend 便可。

  • 二、開啓 ulog 後,系統運行崩潰,例如:線程堆棧溢出

    ulog 比起之前用的 rtdbg 或者 rt_kprintf 打印輸出函數會多佔一部分線程堆棧空間,若是是開啓了浮點數打印支持,因爲其內部使用了 libc 裏資源佔用加大的 vsnprintf,因此堆棧建議多預留 250 字節。若是開啓了時間戳功能,堆棧建議多預留 100 字節。

  • 三、日誌內容的末尾缺失

    這是因爲日誌內容超出設定的日誌的最大寬度。檢查 The log's max width 選項,並增大其至合適的大小。

  • 四、開啓時間戳之後,爲何看不到毫秒級時間

    這是由於 ulog 目前只支持在開啓軟件模擬 RTC 狀態下,顯示毫秒級時間戳。如需顯示,只要開啓 RT-Thread 軟件模擬 RTC 功能便可。

  • 五、每次 include ulog 頭文件前,都要定義 LOG_TAGLOG_LVL ,能否簡化

    LOG_TAG 若是不定義,默認會使用 NO_TAG 標籤,這樣輸出的日誌會容易產生誤解,因此標籤的宏不建議省略。

    LOG_LVL 若是不定義,默認會使用調試級別,若是該模塊處於開發階段這個過程能夠省略,可是模塊代碼若是已經穩定,建議定義該宏,並修改級別爲信息級別。

五、參考

本文全部相關的 API

API 列表

API 位置
int ulog_init(void) ulog.c
void ulog_deinit(void) ulog.c
LOG_E(…) / LOG_W(…) / LOG_I(…) / LOG_D(…) / LOG_RAW(…) ulog.h
ulog_e(TAG, …) / ulog_w(TAG, …) / ulog_i(TAG, …) / ulog_d(TAG, …) ulog_def.h
void ulog_hexdump(const char *name, rt_size_t width, rt_uint8_t *buf, rt_size_t size) ulog.c

核心 API 詳解

ulog 初始化
1int ulog_init(void)
2

在使用 ulog 前必須調用該函數完成 ulog 初始化。若是開啓了組件自動初始化, API 也將被自動調用。

返回 描述
>=0 成功
-5 失敗,內存不足
ulog 反初始化
1void ulog_deinit(void)

當 ulog 再也不使用時,能夠執行該 deinit 釋放資源。

LOG_X 日誌輸出 API
1LOG_X(...)

該 API 是一個宏,X 對應的是不一樣級別的第一個字母大寫。

注意:使用這個 API 前需在先在 ulog.h 頭文件上方,定義 LOG_TAG  及 LOG_LVL  宏,詳見:日誌輸出 API 的使用章節。

這個 API 使用就能夠基於已定義好的標籤,輸出對應級別的日誌。

參數 描述
日誌內容,格式與 printf 一致
ulog_x 日誌輸出 API
1ulog_x(TAG, ...)

該 API 是一個宏,x 對應的是不一樣級別的第一個字母小寫。

注意:使用這個 API 前需在先在 ulog.h 頭文件上方,定義 LOG_LVL  宏,詳見:日誌輸出 API 的使用章節。

這個 API 可在輸出對應級別的日誌時指定標籤,比 LOG_X 多一個入參,不推薦使用。

參數 描述
TAG 日誌標籤
日誌內容,格式與 printf 一致
輸出 hex 格式日誌
1void ulog_hexdump(const char *tag, rt_size_t width, rt_uint8_t *buf, rt_size_t size)

以 16 進制 hex 格式 dump 數據到日誌中,使用方法及效果詳見 hexdump 章節

注意:

  • hexdump 日誌爲 DEBUG 級別,並支持運行期的級別過濾

  • hexdump 日誌對應的 tag ,支持運行期的標籤過濾

參數 描述
tag 日誌標籤
width 一行 hex 內容的寬度(數量)
buf 待輸出的數據內容
size 數據大小


近期活動


活動1

 

資料下載:2018深圳開發者大會-PPT及培訓資料下載,請在公衆號後臺回覆關鍵詞「深圳開發者大會」,請勿錯字、漏字、多打空格,不然系統沒法識別。

活動2


柿餅GUI直播資料:

請添加小師妹(微信:RT-Thread2006)回覆「柿餅」下載。

加我爲好友


你能夠添加微信13924608367爲好友,註明:公司+姓名,拉進 RT-Thread 官方微信交流羣

RT-Thread


讓物聯網終端的開發變得簡單、快速,芯片的價值獲得最大化發揮。GPLv2+協議,可免費在商業產品中使用。

長按二維碼,關注咱們


  點「閱讀原文」 進入RT-Thread論壇

本文分享自微信公衆號 - RTThread物聯網操做系統(RTThread)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索