0x0 目的
通過打印log信息輔助排查問題,在不方便單步調試場景下(例如Android NDK開發、嵌入式linux開發),輔助定位。
最終效果:
0x1 不用 cout
用於打印輸出信息,最直白的是cout和printf/fprintf;不應該用cout的一個原因是,它會增加鏈接的obj文件,使生成的動態庫/可執行文件猛增(Android NDK):在屏幕輸出"hello world",不同的實現下,可執行文件的大小:
- iostrea(cout): 4557KB
- printf: 9KB
- printf+string類: 1214KB
cout比printf足足多了4MB大小,這是不必要開銷,因此我們不用cout。
0x2 不直接用 printf
假設用C/C++實現的算法,作爲Android App的NDK模塊被集成;當需要排錯時,App運行情況下在adb logcat裏看,使用的是__android_log_print
;如果是算法自身在設備驗證,編譯成可執行文件後在root過的手機(或開發板)的adb shell裏運行,則需要用printf
或fprintf
,__android_log_print
反而不可用。兼容這兩種情況的做法是包裝一層,可以是函數級的,也可以是宏級的;提供接口形如LOGD()
和LOGE()
。
0x3 用宏實現,而不用函數實現
用函數實現log功能是沒有問題的,但怎麼用這個實現則是需要考慮的:
-
函數級的實現:應當作爲log庫存在,每個需要log的模塊都鏈接它而非源碼包含它;但對於不怎麼讓用依賴庫的場景下,把log的實現包含在各個模塊中,每個模塊能快速開發;隱患是各個模塊編譯後可能產生同名符號,集成多個模塊時觸發鏈接問題,並且由於不同編譯器對應的鏈接器的鏈接策略不同,導致PC和設備結果不一致。
-
宏級的實現:不夠模塊化,但能迅速集成到各個模塊,也不存在符號鏈接問題;不應該在模塊對外提供的API頭文件中被包含,因爲會造成宏重複定義,應當在用於實現的代碼中被包含。
0x4 簡易實現
0x41 最簡實現
只提供LOGD
一個接口,實現也是簡單粗暴:
#define LOGD printf
此實現確實能用format串,但缺點也很明顯:不能hook,即不能額外輸出行號、所在文件,也不能自動換行。
其實,如果是要快速開發,這句定義足矣。
0x42 打印行號、文件名、自動換行
這裏的技巧是,給宏傳入不定數量參數後,怎麼去用它們,使用了__VA_ARGS__
宏,並且使用連字符##
避免了format爲空時編譯報錯。逐步實現的過程記錄如下:
// 使用...表示宏的不定參數。不過行號和文件名並沒有被輸出,用戶自行指定的format也沒有被用上
//#define LOGD(...) printf("%s line=%d file=%s\n", ##__VA_ARGS__, __LINE__, __FILE__)
//把用戶指定的format單獨拿出來
//但如果用戶的format串裏沒有格式,也就是LOGD()裏頭只有一個雙引號引起來的部分,此時編譯失敗
//#define LOGD(fmt, ...) printf(fmt, __VA_ARGS__)
// 使用##__VA_ARGS__, 避免了LOGD("xxx")編譯報錯
//#define LOGD(fmt, ...) printf(fmt, ##__VA_ARGS__)
// 使用()把預定義的格式串和用戶輸入的格式串包起來,隱式構造了完整的格式串
// 這使得希望固定增加的行號、文件名、換行得以輸出
//#define LOGD(fmt, ...) printf( ("line=%d file=%s " fmt "\n"), __LINE__, __FILE__, ##__VA_ARGS__)
0x43 stdout/stderr選擇、android平臺支持
同上,這裏的實現並非最終結果,但如果想試着瞭解演進過程,不妨看看
// 使用fprintf而不是printf,能夠區分stdout和stderr,可分別用於debug和error兩級log
// 當集成多個算法模塊時,每個模塊的log打印,應當有獨立tag,用於和別的模塊區分開來,便於搜索和快速定位問題
// 先定義一個LOGDT表示Debug級別的log宏的模板,允許傳入fmt和tag;然後定義LOGD爲:tag是DEFAULT_TAG的LOGDT調用
// 使用__PRETTY_FUNCTION__ 打印出函數名和傳入的參數類型
//#define DEFAULT_TAG "pixel"
//#define LOGDT(fmt, tag, ...) fprintf(stdout, ("Debug/%s: %s [File %s][Line %d] " fmt "\n"), tag, __PRETTY_FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__)
//#define LOGD(fmt, ...) LOGDT(fmt, DEFAULT_TAG, ##__VA_ARGS__)
// android平臺NDK的打印,包括兩種情況:
// 1)集成在APP裏的.so,此時用__android_log_print函數,打印內容在logcat裏看到
// 2)console application,在root過的android手機上,或者專門的開發板上,編譯出的可執行程序裏,此時__android_log_print打印不會被看到,仍然需要fprintf
// 這兩種情況的區分,無法通過編譯器預定義的宏來判斷,因此兩種函數的輸出都要調用;行尾使用\來連接多次函數調用
//#define DEFAULT_TAG "pixel"
//#ifdef ANDROID
//#include <android/log.h>
//#define LOGDT(fmt, tag, ...) \
// __android_log_print(ANDROID_LOG_DEBUG, tag, ("%s: %s [File %s][Line %d] " fmt "\n"), __PRETTY_FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); \
// fprintf(stdout, ("Debug/%s: %s [File %s][Line %d] " fmt "\n"), tag, __PRETTY_FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__)
//#define LOGD(fmt, ...) LOGDT(fmt, DEFAULT_TAG, ##__VA_ARGS__)
//#else
//#define LOGDT(fmt, tag, ...) \
// fprintf(stdout, ("Debug/%s: %s [File %s][Line %d] " fmt "\n"), tag, __PRETTY_FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__)
//#endif // ANDROID
//#define LOGD(fmt, ...) LOGDT(fmt, DEFAULT_TAG, ##__VA_ARGS__)
0x44 顯示顏色和運行時間
顏色的顯示,是通過ANSI顏色轉義字符實現的;運行時間,則是通過調用localtime函數實現的。
依然是貼出演進過程:
// 在STDIO情況下的log,帶顏色的話,可以區分不同level的警告;通常error是紅色,是必須消除的warning;
// 基於ANSI的顏色轉義規則可以做到這點;android apk裏頭的顏色,我們暫時不管;
// 簡單起見,先只考慮debug級log的顏色
//#define log_colors_debug "\x1b[94m"
//#define LOGD(fmt, ...) \
// fprintf(stdout, ("%s[%-5s]\x1b[0m" fmt "\n"), log_colors_debug, "debug", ##__VA_ARGS__)
//#define LOGD(fmt, ...) fprintf(stdout, (fmt "\n"), ##__VA_ARGS__)
//#define LOGE(fmt, ...) fprintf(stderr, (fmt "\n"), ##__VA_ARGS__)
//#define LOGT(fd, fmt, ...) fprintf(fd, (fmt "\n"), ##__VA_ARGS__)
//#define LOGD(fmt, ...) LOGT(stdout, fmt, ##__VA_ARGS__)
//#define LOGE(fmt, ...) LOGT(stderr, fmt, ##__VA_ARGS__)
//#define DEFAULT_TAG "pixel"
//#define LOGT(fd, tag, fmt, ...) fprintf(fd, ("%s/[File %s][Line %d] %s" fmt "\n"), tag, __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)
//#define LOGD(fmt, ...) LOGT(stdout, DEFAULT_TAG, fmt, ##__VA_ARGS__)
//#define LOGE(fmt, ...) LOGT(stderr, DEFAULT_TAG, fmt, ##__VA_ARGS__)
//#define DEFAULT_TAG "pixel"
//#define LOGT(level, fmt, tag, fd, ...) fprintf(fd, ("%s/%s [File %s][Line %d] %s" fmt "\n"), tag, level, __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)
//#define LOGD(fmt, ...) LOGT("DEBUG", fmt, DEFAULT_TAG, stdout, ##__VA_ARGS__)
//#define LOGE(fmt, ...) LOGT("ERROR", fmt, DEFAULT_TAG, stderr, ##__VA_ARGS__)
//#define DEFAULT_TAG "pixel"
//#define LOGT(level, fmt, tag, fd, color, ...) fprintf(fd, ("%s/ %s[%-5s]\x1b[0m [File %s][Line %d] %s" fmt "\n"), tag, color, level, __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)
//#define LOGD(fmt, ...) LOGT("DEBUG", fmt, DEFAULT_TAG, stdout, "\x1b[36m", ##__VA_ARGS__)
//#define LOGE(fmt, ...) LOGT("ERROR", fmt, DEFAULT_TAG, stderr, "\x1b[31m", ##__VA_ARGS__)
// 顯示當前運行時間,也是很有用的信息
//#include <time.h>
//static inline char* timenow() {
// time_t rawtime = time(NULL);
// struct tm* timeinfo = localtime(&rawtime);
// static char now[64];
// // if timezone required, use %z
// //now[strftime(now, sizeof(now), "%Y-%m-%d %H:%M:%S(%z)", timeinfo)] = '\0';
// now[strftime(now, sizeof(now), "%Y-%m-%d %H:%M:%S", timeinfo)] = '\0';
// return now;
//}
//#define LOGT(fmt, fd, ...) fprintf(fd, ("%s " fmt "\n"), timenow(), ##__VA_ARGS__)
//#define LOGD(fmt, ...) LOGT(fmt, stdout, ##__VA_ARGS__)
//#define LOGE(fmt, ...) LOGT(fmt, stderr, ##__VA_ARGS__)
//#include <time.h>
//static inline char* timenow() {
// time_t rawtime = time(NULL);
// struct tm* timeinfo = localtime(&rawtime);
// static char now[64];
// // if timezone required, use %z
// //now[strftime(now, sizeof(now), "%Y-%m-%d %H:%M:%S(%z)", timeinfo)] = '\0';
// now[strftime(now, sizeof(now), "%Y-%m-%d %H:%M:%S", timeinfo)] = '\0';
// return now;
//}
//#define DEFAULT_TAG "pixel"
//#define LOGT(level, fmt, tag, fd, color, ...) fprintf(fd, ("%s | %s | %s[%-5s]\x1b[0m [File %s][Line %d] %s" fmt "\n"), timenow(), tag, color, level, __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)
//#define LOGD(fmt, ...) LOGT("DEBUG", fmt, DEFAULT_TAG, stdout, "\x1b[36m", ##__VA_ARGS__)
//#define LOGE(fmt, ...) LOGT("ERROR", fmt, DEFAULT_TAG, stderr, "\x1b[31m", ##__VA_ARGS__)
0x5 可維護性版本的實現
雖然上述實現,包含了豐富的格式以及顏色;但如果某天不想要時間戳、不想要行號呢?或者,只是不想要函數名?修改起來比較棘手。
爲了更好的可維護性,參照開源項目 macro-logger ,該項目中還定義了 LOG_IF_ERROR 宏,這和Caffe中的CHECK_IF,或者ASSERT_IF,比較類似。
0x51 行間連接符,以及do{...} while(0) 增加安全性
在android平臺,同時調用 __android_log_print 和 fprintf,放在兩行用連行符\
連接。
用 do{...} while(0) 把宏定義的實現做一層包裝,其中...表示原本的實現,目的是當有人寫if/else不帶括號時依然能正確使用:
if(x<y)
LOGD("x<y");
else
LOGD("x>=y");
擴展爲
if(x<y)
do {
__android_log_print(ANDROID_LOG_DEBUG, tag, "x<y\n");
printf("x<y\n");
} while(0)
else
do {
__android_log_print(ANDROID_LOG_DEBUG, tag, "x>=y\n");
printf("x>=y\n");
} while(0)
而不是
if(x<y)
__android_log_print(ANDROID_LOG_DEBUG, tag, "x<y\n");
printf("x<y\n"); //注意這一行導致if/else被分隔,下一行else編譯報錯
else
__android_log_print(ANDROID_LOG_DEBUG, tag, "x>=y\n");
printf("x>=y\n");
0x52 統一使用stdout,不用stderr
當在PC上執行的腳本,是push可執行程序到adb shell,並把adb shell上的執行結果取回來時,LOGE調用stderr,LOGD調用stdout;stderr的結果可能出現在stdout前,換言之log順序被破壞了。
統一用stdout,當然如果要修改,目前也是支持的,因爲定義LOGD/LOGE時候會指定fd;只不過目前把fd都寫成stdout。
0x503 和 Android log 的兼容
爲了和 Android log 兼容,定義了枚舉類型的 Priority:
// Compatible with Android NDK
enum PixelLogPriority {
PIXEL_LOG_DEBUG = 3,
PIXEL_LOG_INFO,
PIXEL_LOG_WARN,
PIXEL_LOG_ERROR,
};
const static char* g_pixel_log_priority_str[7] = {
0, 0, 0,
"Debug",
"Info",
"Warn",
"Error"
};
0x504 避免宏定義衝突
雖然前面說到,用宏實現的log工具,應當只被SDK的實現代碼(而非對外API)文件中包含;但如果用了其他依賴庫,有同名的宏,會導致想不到的編譯問題(本人有幸遇到過,祖傳代碼中定義的宏 _EPS,和 OpenCV 中的枚舉類型元素同名衝突,編譯時報很奇怪的錯誤)。
因此這裏統一把對外用的宏(以及打印時間的那個函數),用PIXEL_
開頭;其他宏用_PXL_
開頭。
0x6 完整實現
/*********************************************************************************
* [Filenname]: pixel_log.h
* [Datetime]: 2021-01-23 15:00:17
* [Author]: ChrisZZ
*********************************************************************************
* [Usage]:
* const char* name = "ChrisZZ";
* PIXEL_LOGD("hello, %s", name);
* PIXEL_LOGI("This is an info message");
* PIXEL_LOGE("I am error !");
* int n = 5;
* PIXEL_LOG_IF_ERROR(n!=0, "n is not zero, this is error");
*
* [Customization]:
* 1. Turn on/off one log level by modify values of:
* _PXL_LOG_DEBUG_ENABLED
* _PXL_LOG_INFO_ENABLED
* _PXL_LOG_WARN_ENABLED
* _PXL_LOG_ERROR_ENABLED
* 2. Select pre-defined log format by define to 1 in **only one** of:
* _PXL_LOGFMT_FULL
* _PXL_LOGFMT_MEDIUM
* _PXL_LOGFMT_SIMPLE
*********************************************************************************/
#include <time.h>
#include <string.h>
#ifdef ANDROID
#include <android/log.h>
#endif
// Compatible with Android NDK
enum PixelLogPriority {
PIXEL_LOG_DEBUG = 3,
PIXEL_LOG_INFO,
PIXEL_LOG_WARN,
PIXEL_LOG_ERROR,
};
const static char* g_pixel_log_priority_str[7] = {
0, 0, 0,
"Debug",
"Info",
"Warn",
"Error"
};
//--------------------------------------------------------------------------------
// => switches for each log level
// [user setting]
//--------------------------------------------------------------------------------
#define _PXL_LOG_DEBUG_ENABLED 1
#define _PXL_LOG_INFO_ENABLED 1
#define _PXL_LOG_WARN_ENABLED 1
#define _PXL_LOG_ERROR_ENABLED 1
//--------------------------------------------------------------------------------
// => select log format
// [user setting]
//--------------------------------------------------------------------------------
#define _PXL_LOGFMT_FULL 1
#define _PXL_LOGFMT_MEDIUM 0
#define _PXL_LOGFMT_SIMPLE 0
//--------------------------------------------------------------------------------
// => filename
//--------------------------------------------------------------------------------
// only filename
#define _PXL_FILE strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__
// contain folder name such as src/log.cpp
//#define _FILE __FILE__
//--------------------------------------------------------------------------------
// => function
//--------------------------------------------------------------------------------
// only function name
#define _PXL_FUNCTION __FUNCTION__
// function name with parameter types
//#define _FUNCTION __PRETTY_FUNCTION__
//--------------------------------------------------------------------------------
// => tag
//--------------------------------------------------------------------------------
// the tag for current module/library/file
#define _PXL_MODULE_TAG "pixel"
#if _PXL_LOGFMT_FULL
static inline char* timenow() {
time_t rawtime = time(NULL);
struct tm* timeinfo = localtime(&rawtime);
static char now[64];
//--------------------------------------------------------------------------------
// => timestamp format
//--------------------------------------------------------------------------------
#define _PXL_TIMESTAMP_FMT "%Y-%m-%d %H:%M:%S"
// if timezone required, use %z
//#define TIMESTAMP_FORMAT "%Y-%m-%d %H:%M:%S(%z)"
now[strftime(now, sizeof(now), _PXL_TIMESTAMP_FMT, timeinfo)] = '\0';
#undef _PXL_TIMESTAMP_FMT
return now;
}
#endif
//--------------------------------------------------------------------------------
// => log format
//--------------------------------------------------------------------------------
// the param `fmt` is user specified format
// _PXL_LOG_FULL_FMT(fmt) ammed pre-defined stuffs (time, filename, line num, function) with colors
#define _PXL_LOG_FULL_FMT(fmt) ("%s | %s | %s[%-5s]\x1b[0m | %s, line %d, %s | " fmt "\n")
#define _PXL_LOG_FULL_ARGS(tag, color, priority) timenow(), tag, color, g_pixel_log_priority_str[priority], _PXL_FILE, __LINE__, _PXL_FUNCTION
// the param `fmt` is user specified format
// _PXL_LOG_MEDIUM_FMT(fmt) ammed pre-defined stuffs (filename, line num)
#define _PXL_LOG_MEDIUM_FMT(fmt) ("%s/%s%-5s\x1b[0m | %s, line %d | " fmt "\n")
#define _PXL_LOG_MEDIUM_ARGS(tag, color, priority) tag, color, g_pixel_log_priority_str[priority], _PXL_FILE, __LINE__
//--------------------------------------------------------------------------------
// => log template for all levels
//--------------------------------------------------------------------------------
// the full format log
#ifdef ANDROID
#define _PXL_LOGT_FULL(priority, fmt, tag, fd, color, ...) do { \
__android_log_print(priority, tag, _PXL_LOG_FULL_FMT(fmt), _PXL_LOG_FULL_ARGS(tag, color, priority), ##__VA_ARGS__); \
fprintf(fd, _PXL_LOG_FULL_FMT(fmt), _PXL_LOG_FULL_ARGS(tag, color, priority), ##__VA_ARGS__); \
} while(0)
#else
#define _PXL_LOGT_FULL(priority, fmt, tag, fd, color, ...) do { \
fprintf(fd, _PXL_LOG_FULL_FMT(fmt), _PXL_LOG_FULL_ARGS(tag, color, priority), ##__VA_ARGS__); \
} while(0)
#endif
// the medium format log
#ifdef ANDROID
#define _PXL_LOGT_MEDIUM(priority, fmt, tag, fd, color, ...) do { \
__android_log_print(priority, tag, _PXL_LOG_MEDIUM_FMT(fmt), _PXL_LOG_MEDIUM_ARGS(tag, color, priority), ##__VA_ARGS__); \
fprintf(fd, _PXL_LOG_MEDIUM_FMT(fmt), _PXL_LOG_MEDIUM_ARGS(tag, color, priority), ##__VA_ARGS__); \
} while(0)
#else
#define _PXL_LOGT_MEDIUM(priority, fmt, tag, fd, color, ...) do { \
fprintf(fd, _PXL_LOG_MEDIUM_FMT(fmt), _PXL_LOG_MEDIUM_ARGS(tag, color, priority), ##__VA_ARGS__); \
} while(0)
#endif
// simple format log
#ifdef ANDROID
#define _PXL_LOGT_SIMPLE(priority, fmt, ...) do { \
__android_log_print(priority, _PXL_MODULE_TAG, fmt, ##__VA_ARGS__); \
fprintf(stdout, (fmt "\n"), ##__VA_ARGS__); \
} while(0)
#else
#define _PXL_LOGT_SIMPLE(priority, fmt, ...) do { \
fprintf(stdout, (fmt "\n"), ##__VA_ARGS__); \
} while(0)
#endif
//--------------------------------------------------------------------------------
// => log template for each level
//--------------------------------------------------------------------------------
// NOTE: if using stderr, `run-and-install.sh`(start on PC, run on Android) will mess up log order
// For better log order, always use stdout
#if _PXL_LOGFMT_FULL
#define _PXL_LOGDT_FULL(fmt, ...) _PXL_LOGT_FULL(PIXEL_LOG_DEBUG, fmt, _PXL_MODULE_TAG, stdout, "\x1b[36m", ##__VA_ARGS__)
#define _PXL_LOGIT_FULL(fmt, ...) _PXL_LOGT_FULL(PIXEL_LOG_INFO, fmt, _PXL_MODULE_TAG, stdout, "\x1b[32m", ##__VA_ARGS__)
#define _PXL_LOGWT_FULL(fmt, ...) _PXL_LOGT_FULL(PIXEL_LOG_WARN, fmt, _PXL_MODULE_TAG, stdout, "\x1b[33m", ##__VA_ARGS__)
#define _PXL_LOGET_FULL(fmt, ...) _PXL_LOGT_FULL(PIXEL_LOG_ERROR, fmt, _PXL_MODULE_TAG, stdout, "\x1b[31m", ##__VA_ARGS__)
#elif _PXL_LOGFMT_MEDIUM
#define _PXL_LOGDT_MEDIUM(fmt, ...) _PXL_LOGT_MEDIUM(PIXEL_LOG_DEBUG, fmt, _PXL_MODULE_TAG, stdout, "\x1b[36m", ##__VA_ARGS__)
#define _PXL_LOGIT_MEDIUM(fmt, ...) _PXL_LOGT_MEDIUM(PIXEL_LOG_INFO, fmt, _PXL_MODULE_TAG, stdout, "\x1b[32m", ##__VA_ARGS__)
#define _PXL_LOGWT_MEDIUM(fmt, ...) _PXL_LOGT_MEDIUM(PIXEL_LOG_WARN, fmt, _PXL_MODULE_TAG, stdout, "\x1b[33m", ##__VA_ARGS__)
#define _PXL_LOGET_MEDIUM(fmt, ...) _PXL_LOGT_MEDIUM(PIXEL_LOG_ERROR, fmt, _PXL_MODULE_TAG, stdout, "\x1b[31m", ##__VA_ARGS__)
#elif _PXL_LOGFMT_SIMPLE
#define _PXL_LOGDT_SIMPLE(fmt, ...) _PXL_LOGT_SIMPLE(PIXEL_LOG_DEBUG, fmt, ##__VA_ARGS__)
#define _PXL_LOGIT_SIMPLE(fmt, ...) _PXL_LOGT_SIMPLE(PIXEL_LOG_INFO, fmt, ##__VA_ARGS__)
#define _PXL_LOGWT_SIMPLE(fmt, ...) _PXL_LOGT_SIMPLE(PIXEL_LOG_WARN, fmt, ##__VA_ARGS__)
#define _PXL_LOGET_SIMPLE(fmt, ...) _PXL_LOGT_SIMPLE(PIXEL_LOG_ERROR, fmt, ##__VA_ARGS__)
#else
#pragma message("Please define one of _PXL_LOGFMT_FULL, _PXL_LOGFMT_MEDIUM or _PXL_LOGFMT_SIMPLE")
#endif
//--------------------------------------------------------------------------------
// => specify each log level's template
// [user setting]
//--------------------------------------------------------------------------------
// use full format template
#if _PXL_LOGFMT_FULL
#define _PXL_LOGDT(fmt, ...) _PXL_LOGDT_FULL(fmt, ##__VA_ARGS__)
#define _PXL_LOGIT(fmt, ...) _PXL_LOGIT_FULL(fmt, ##__VA_ARGS__)
#define _PXL_LOGWT(fmt, ...) _PXL_LOGWT_FULL(fmt, ##__VA_ARGS__)
#define _PXL_LOGET(fmt, ...) _PXL_LOGET_FULL(fmt, ##__VA_ARGS__)
#elif _PXL_LOGFMT_MEDIUM
#define _PXL_LOGDT(fmt, ...) _PXL_LOGDT_MEDIUM(fmt, ##__VA_ARGS__)
#define _PXL_LOGIT(fmt, ...) _PXL_LOGIT_MEDIUM(fmt, ##__VA_ARGS__)
#define _PXL_LOGWT(fmt, ...) _PXL_LOGWT_MEDIUM(fmt, ##__VA_ARGS__)
#define _PXL_LOGET(fmt, ...) _PXL_LOGET_MEDIUM(fmt, ##__VA_ARGS__)
#elif _PXL_LOGFMT_SIMPLE
#define _PXL_LOGIT(fmt, ...) _PXL_LOGIT_SIMPLE(fmt, ##__VA_ARGS__)
#define _PXL_LOGDT(fmt, ...) _PXL_LOGDT_SIMPLE(fmt, ##__VA_ARGS__)
#define _PXL_LOGWT(fmt, ...) _PXL_LOGWT_SIMPLE(fmt, ##__VA_ARGS__)
#define _PXL_LOGET(fmt, ...) _PXL_LOGET_SIMPLE(fmt, ##__VA_ARGS__)
#else
#pragma message("Please define one of _PXL_LOGFMT_FULL, _PXL_LOGFMT_MEDIUM or _PXL_LOGFMT_SIMPLE")
#endif
//--------------------------------------------------------------------------------
// => enable log macro according to LOG_LEVEL
//--------------------------------------------------------------------------------
#if _PXL_LOG_INFO_ENABLED
#define PIXEL_LOGI(fmt, ...) _PXL_LOGIT(fmt, ##__VA_ARGS__)
#else
#define PIXEL_LOGI(fmt, ...)
#endif
#if _PXL_LOG_DEBUG_ENABLED
#define PIXEL_LOGD(fmt, ...) _PXL_LOGDT(fmt, ##__VA_ARGS__)
#else
#define PIXEL_LOGD(fmt, ...)
#endif
#if _PXL_LOG_WARN_ENABLED
#define PIXEL_LOGW(fmt, ...) _PXL_LOGWT(fmt, ##__VA_ARGS__)
#else
#define PIXEL_LOGW(fmt, ...)
#endif
#if _PXL_LOG_ERROR_ENABLED
#define PIXEL_LOGE(fmt, ...) _PXL_LOGET(fmt, ##__VA_ARGS__)
#else
#define PIXEL_LOGE(fmt, ...)
#endif
#if _PXL_LOG_ERROR_ENABLED
#define PIXEL_LOG_IF_ERROR(condition, fmt, ...) do { \
if (condition) PIXEL_LOGE(fmt, ##__VA_ARGS__); \
} while(0)
#else
#define PIXEL_LOG_IF_ERROR(condition, fmt, ...)
#endif
0x7 總結和參考
用宏實現了log工具的過程中,增加了宏定義的使用熟練度(尤其是##__VA_ARGS__
,調試期間也發現仍然不如函數實現方式便於調試;基於先前的一次嘗試實現,以及開源項目,這次的實現比較順利,聚焦在一個點上持續演進的方法被再次證明有用;當然,多線程安全、和其他log庫(如spdlog)的對比,這些是目前缺失的,仍然需要一定的積累和實踐。
相關參考鏈接和說明:
-
編寫合格的C代碼(2):實現簡易日誌庫
這是一篇舊blog,用函數方式實現了log庫,並演示了ANSI顏色轉義字符的顯示 -
C語言中do...while(0)的妙用
展示了do...while(0)的必要性 -
macro-logger
展示了宏定義實現的log庫,包括可配置格式串等 -
ncnn中的log宏定義
其實ncnn中的NCNN_LOGE的實現很簡潔也基本夠用了,推薦一下