EasyLogger,一款輕量級且高性能的日志庫
嵌入式開源項目精選專欄
本專欄由Mculover666創(chuàng)建,主要內(nèi)容為尋找嵌入式領(lǐng)域內(nèi)的優(yōu)質(zhì)開源項目,一是幫助開發(fā)者使用開源項目實現(xiàn)更多的功能,二是通過這些開源項目,學(xué)習(xí)大佬的代碼及背后的實現(xiàn)思想,提升自己的代碼水平,和其它專欄相比,本專欄的優(yōu)勢在于:
不會單純的介紹分享項目,還會包含作者親自實踐的過程分享,甚至還會有對它背后的設(shè)計思想解讀。
目前本專欄包含的開源項目有:
-
SFUD | 一個簡潔實用的開源項目,幫你輕松搞定SPI Flash -
cJSON | 一個輕量級C語言JSON解析器 -
paho | 支持10種語言編寫mqtt客戶端,總有一款適合你! -
MultiButton | 一個小巧簡單易用的事件驅(qū)動型按鍵驅(qū)動模塊 -
letter-shell | 一個功能強大的嵌入式shell
如果您自己編寫或者發(fā)現(xiàn)的開源項目不錯,歡迎留言或者私信投稿到本專欄,分享獲得雙倍的快樂!
1. EasyLogger
本期給大家?guī)淼拈_源項目是 EasyLogger,一款輕量級且高性能的日志庫,作者armink,目前收獲 1.1K 個 star,遵循 MIT 開源許可協(xié)議。
EasyLogger 是一款超輕量級、高性能的 C/C++ 日志庫,非常適合對資源敏感的軟件項目,相比之下, EasyLogger 的功能更加簡單,提供給用戶的接口更少,上手會更快,更多實用功能支持以插件形式進行動態(tài)擴展。
目前EasyLogger支持以下功能:
-
日志輸出方式支持串口、Flash、文件等; -
日志內(nèi)容可包含級別、時間戳、線程信息、進程信息等; -
支持多種操作系統(tǒng),支持裸機; -
各級別日志支持不同顏色顯示;
項目地址:https://github.com/armink/EasyLogger
2. 移植EasyLogger
2.1. 移植思路
在移植過程中主要參考兩個資料:項目的readme文檔和demo工程。
對于這些開源項目,其實移植起來也就兩步:
-
① 添加源碼到裸機工程中; -
② 實現(xiàn)需要的接口即可;
2.2. 準備裸機工程
本文中我使用的是小熊派IoT開發(fā)套件,主控芯片為STM32L431RCT6:移植之前需要準備一份裸機工程,我使用STM32CubeMX生成,使用USART1的查詢方式發(fā)送數(shù)據(jù),并將printf重定向到USART1,具體過程請參考:
-
STM32CubeMX_06 | 使用USART發(fā)送和接收數(shù)據(jù)(查詢模式) -
STM32CubeMX_09 | 重定向printf函數(shù)到串口輸出的多種方法
串口USART1配置如下:生成工程后printf重定向代碼如下:
#include <stdio.h>
int fputc(int ch, FILE *stream)
{
/* 堵塞判斷串口是否發(fā)送完成 */
while((USART1->ISR & 0X40) == 0);
/* 串口發(fā)送完成,將該字符發(fā)送 */
USART1->TDR = (uint8_t) ch;
return ch;
}
裸機工程準備好之后開始移植easylogger。
2.3. 添加elog到工程中
① 復(fù)制源碼到工程中:② 在keil中添加easylogger組件的源碼文件:
-
port/elog_port.c
:elog移植接口文件; -
src/elog.c
:elog核心功能源碼; -
src/elog_utils.c
:elog所用到的一些c庫工具函數(shù)實現(xiàn); -
src/elog_buf.c
(可選添加):elog緩沖輸出模式源碼; -
src/elog_async.c
(可選添加):elog異步輸出模式源碼;
③ 將easylogger/inc
頭文件路徑添加到keil中:
2.4. 實現(xiàn)elog移植接口
elog的移植接口都已經(jīng)寫好了,在elog_port.c
文件中,只需要在函數(shù)體中添加代碼即可。
① elog初始化接口
ElogErrCode elog_port_init(void);
如果涉及到后續(xù)elog使用資源的初始化,比如動態(tài)申請分配緩沖區(qū)內(nèi)存,可以放在此接口中,本文中保持默認。
② elog日志輸出接口(重點)
//開頭添加
#include <stdio.h>
……
//接口實現(xiàn)
void elog_port_output(const char *log, size_t size) {
//日志使用printf輸出,printf已經(jīng)重定向到串口USART1
printf("%.*s", size, log);
}
這兒有個小知識點,%s
表示字符串輸出,.<十進制數(shù)>
是精度控制格式符,輸出字符時表示輸出字符的位數(shù),在精度控制時,小數(shù)點后的十進制數(shù)可以使用*
來占位,在后面提供一個變量作為精度控制的具體值。
③ 日志輸出上鎖/解鎖接口
該接口可以對日志輸出接口進行上鎖/解鎖,以保證日志在并發(fā)輸出時的正確性,本文中使用的是裸機程序,所以在此使用關(guān)閉全局中斷來加鎖,打開全局中斷來解鎖:
//開頭添加
#include <stm32l4xx_hal.h>
……
//接口實現(xiàn)
void elog_port_output_lock(void) {
//關(guān)閉全局中斷
__set_PRIMASK(1);
}
void elog_port_output_unlock(void) {
//開啟全局中斷
__set_PRIMASK(0);
}
STM32開關(guān)全局中斷的方式很多,本文中直接操作 PRIMASK 寄存器來快速的屏蔽/打開全局中斷,參考文章:
https://blog.csdn.net/working24hours/article/details/88323241
④ 系統(tǒng)信息獲取接口
elog提供了三個接口用來獲取當前時間、獲取進程號、獲取線程號,因為本文中移植到裸機工程中,并且沒有提供時間支持,所以這三個接口都返回空字符串,如下:
const char *elog_port_get_time(void) {
return "";
}
const char *elog_port_get_p_info(void) {
return "";
}
const char *elog_port_get_t_info(void) {
return "";
}
2.5. 配置elog
elog的核心功能開啟宏定義和核心參數(shù)宏定義都在配置文件elog_cfg.h
中,在本文中只講述其中重要的宏定義。
日志輸出總開關(guān):
/* enable log output. */
#define ELOG_OUTPUT_ENABLE
換行符宏定義修改如下:
/* output newline sign */
#define ELOG_NEWLINE_SIGN "\r\n"
帶有顏色的日志輸出開關(guān):
/* enable log color */
#define ELOG_COLOR_ENABLE
移植時并沒有添加異步輸出和緩沖區(qū)輸出的源碼,所以將這兩個功能關(guān)掉:至此,移植配置完成,接下來可以開始愉快的使用啦!
3. 使用easylogger
3.1. 初始化elog
elog使用之前需要初始化,過程有三步:① 初始化elog
ElogErrCode elog_init(void);
② 設(shè)置日志輸出格式
void elog_set_fmt(uint8_t level, size_t set);
其中第一個參數(shù)表示設(shè)置哪個日志輸出級別對應(yīng)的輸出格式,從以下宏定義中選擇一個:
/* output log's level */
#define ELOG_LVL_ASSERT 0
#define ELOG_LVL_ERROR 1
#define ELOG_LVL_WARN 2
#define ELOG_LVL_INFO 3
#define ELOG_LVL_DEBUG 4
#define ELOG_LVL_VERBOSE 5
其二個參數(shù)是日志輸出格式,枚舉給出,可以自由組合搭配:
/* all formats index */
typedef enum {
ELOG_FMT_LVL = 1 << 0, /**< level */
ELOG_FMT_TAG = 1 << 1, /**< tag */
ELOG_FMT_TIME = 1 << 2, /**< current time */
ELOG_FMT_P_INFO = 1 << 3, /**< process info */
ELOG_FMT_T_INFO = 1 << 4, /**< thread info */
ELOG_FMT_DIR = 1 << 5, /**< file directory and name */
ELOG_FMT_FUNC = 1 << 6, /**< function name */
ELOG_FMT_LINE = 1 << 7, /**< line number */
} ElogFmtIndex;
/* macro definition for all formats */
#define ELOG_FMT_ALL (ELOG_FMT_LVL|ELOG_FMT_TAG|ELOG_FMT_TIME|ELOG_FMT_P_INFO|ELOG_FMT_T_INFO| ELOG_FMT_DIR|ELOG_FMT_FUNC|ELOG_FMT_LINE)
③ 啟動elog
void elog_start(void);
接下來在main函數(shù)中的usart1初始化函數(shù)之后,while(1)之前編寫elog初始化代碼:
/* USER CODE BEGIN 2 */
/* 初始化elog */
elog_init();
/* 設(shè)置每個級別的日志輸出格式 */
//輸出所有內(nèi)容
elog_set_fmt(ELOG_LVL_ASSERT, ELOG_FMT_ALL);
//輸出日志級別信息和日志TAG
elog_set_fmt(ELOG_LVL_ERROR, ELOG_FMT_LVL | ELOG_FMT_TAG);
elog_set_fmt(ELOG_LVL_WARN, ELOG_FMT_LVL | ELOG_FMT_TAG);
elog_set_fmt(ELOG_LVL_INFO, ELOG_FMT_LVL | ELOG_FMT_TAG);
//除了時間、進程信息、線程信息之外,其余全部輸出
elog_set_fmt(ELOG_LVL_DEBUG, ELOG_FMT_ALL & ~(ELOG_FMT_TIME | ELOG_FMT_P_INFO | ELOG_FMT_T_INFO));
//輸出所有內(nèi)容
elog_set_fmt(ELOG_LVL_VERBOSE, ELOG_FMT_ALL);
/* 啟動elog */
elog_start();
/* USER CODE END 2 */
3.2. elog日志輸出
elog中每種級別都有一種完整方式,兩種簡化方式,使用時自行選擇:
#define elog_assert(tag, ...)
#define elog_a(tag, ...) //簡化方式1,每次需填寫 LOG_TAG
#define log_a(...) //簡化方式2,LOG_TAG 在文件頂部定義,使用前無需填寫 LOG_TAG
#define elog_error(tag, ...)
#define elog_e(tag, ...)
#define log_e(...)
#define elog_warn(tag, ...)
#define elog_w(tag, ...)
#define log_w(...)
#define elog_info(tag, ...)
#define elog_i(tag, ...)
#define log_i(...)
#define elog_debug(tag, ...)
#define elog_d(tag, ...)
#define log_d(...)
#define elog_verbose(tag, ...)
#define elog_v(tag, ...)
#define log_v(...)
前兩種在使用的時候只需要包含<elog.h>
頭文件即可,第三種方式除了包含頭文件之外,還需要在文件開始定義TAG宏定義,使用起來和printf相同,所以這里我使用第三種方法演示。
首先在main.c文件開始定義TAG宏,包含頭文件:
/* USER CODE BEGIN Includes */
#define LOG_TAG "main"
#include <elog.h>
/* USER CODE END Includes */
然后在main函數(shù)中編寫的elog初始化代碼之后,繼續(xù)添加代碼,測試elog的使用:
log_a("Hello EasyLogger!");
log_e("Hello EasyLogger!");
log_w("Hello EasyLogger!");
log_i("Hello EasyLogger!");
log_d("Hello EasyLogger!");
log_v("Hello EasyLogger!");
編譯,燒寫,使用串口終端(Mobaxterm)查看串口輸出:
3.3. 五彩繽紛的輸出
要想五彩繽紛的日志,僅在elog_cfg.h
中使能顏色輸出還不夠,還需要使用API開啟輸出:
void elog_set_text_color_enabled(bool enabled);
在初始化elog的時候使能文字顏色輸出:再次編譯、下載、查看輸出:每個級別日志的前景色、背景色、字體都可以在elog_cfg.h
中修改宏定義,宏定義的值在elog.c
中給出,可自行查看,比如這里我將ERROR級別的日志修改為閃爍字體:編譯、下載、查看輸出:
3.4. 移植前后內(nèi)存占用情況
移植前的裸機工程只具有usart1收發(fā)功能,移植easylogger之后兩者內(nèi)存對比如下:
3.5. elog的高級功能
elog除了基本的日志功能之外,還提供了一些高級功能,比如:
-
日志輸出過濾功能:可以按級別、TAG、關(guān)鍵詞過濾日志; -
緩沖輸出模式; -
異步輸出模式;
這些功能如何使用,在項目的readme文檔中講述的很詳細,本文限于篇幅,這些高級功能不詳細講述,如有興趣深入,可以自行研究。
4. 設(shè)計思想解讀
4.1. 數(shù)據(jù)加工
使用日志打印組件與使用printf最基本的區(qū)別在于:輸出了更多有利于調(diào)試的信息,可以理解為對輸出數(shù)據(jù)進行了一次加工。
打印語句所在文件、函數(shù)名、行號這些信息是利用了編譯器內(nèi)置宏的功能:
-
__FILE__
:文件名 -
__FUNCTION__
:函數(shù)名 -
__LINE__
:行號
而在終端中輸出有顏色的字符則是利用了ANSI escape code,即Escape 序列屏幕控制碼,關(guān)于這兩個知識點詳細的解釋和示例請閱讀:
-
編譯器宏詳解 -
ANSI escape code詳解
在elog中對輸出內(nèi)容進行加工處理的函數(shù)為:
/**
* output the log
*
* @param level level
* @param tag tag
* @param file file name
* @param func function name
* @param line line number
* @param format output format
* @param ... args
*
*/
void elog_output(uint8_t level, const char *tag, const char *file, const char *func,const long line, const char *format, ...) ;
4.2. 日志輸出模式
俗話說,師傅領(lǐng)進門,修行在個人,本文所講述的只是日志打印組件的基本功能,使用printf直接實現(xiàn)日志輸出接口,所以在日志輸出模式上和使用printf輸出沒有區(qū)別,只不過多了些信息。
elog支持異步輸出模式,開啟異步輸出模式后,將會提升用戶應(yīng)用程序的執(zhí)行效率。應(yīng)用程序在進行日志輸出時,無需等待日志徹底輸出完成,即可直接返回。
elog也支持緩沖輸出模式,開啟緩沖輸出模式后,如果緩沖區(qū)不滿,用戶線程在進行日志輸出時,無需等待日志徹底輸出完成,即可直接返回。但當日志緩沖區(qū)滿以后,將會占用用戶線程,自動將緩沖區(qū)中的日志全部輸出干凈。
這兩種無需等待,直接返回的日志輸出模式,在打印大量日志信息的時候非常重要,打印日志的代碼對正常應(yīng)用程序的影響越小越好,本文不再講述,還請讀者自行研究。
5. 項目工程源碼獲取和問題交流
目前我將Easylogger源碼、我移植到小熊派STM32L431RCT6開發(fā)板的工程源碼上傳到了QQ群里(包含好幾份HAL庫,QQ相對速度快點),可以在QQ群里下載,有問題也可以在群里交流,當然也歡迎大家分享出來自己移植的工程到QQ群里:
放上QQ群二維碼:
接收更多精彩文章及資源推送,歡迎訂閱我的微信公眾號:『mculover666』。
免責聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!