C语言实现简易log工具

0x0 目的

通过打印log信息辅助排查问题,在不方便单步调试场景下(例如Android NDK开发、嵌入式linux开发),辅助定位。

最终效果:

C语言实现简易log工具

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里运行,则需要用 printffprintf__android_log_print反而不可用。兼容这两种情况的做法是包装一层,可以是函数级的,也可以是宏级的;提供接口形如 LOGD()LOGE()

0x3 用宏实现,而不用函数实现

用函数实现log功能是没有问题的,但怎么用这个实现则是需要考虑的:

  1. 函数级的实现:应当作为log库存在,每个需要log的模块都链接它而非源码包含它;但对于不怎么让用依赖库的场景下,把log的实现包含在各个模块中,每个模块能快速开发;隐患是各个模块编译后可能产生同名符号,集成多个模块时触发链接问题,并且由于不同编译器对应的链接器的链接策略不同,导致PC和设备结果不一致。
  2. 宏级的实现:不够模块化,但能迅速集成到各个模块,也不存在符号链接问题;不应该在模块对外提供的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
//#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
//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
//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");

扩展为

if(x=y\n");
        printf("x>=y\n");
    } while(0)

而不是

if(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
#include

#ifdef ANDROID
#include
#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)的对比,这些是目前缺失的,仍然需要一定的积累和实践。

相关参考链接和说明:

  1. 编写合格的C代码(2):实现简易日志库
    这是一篇旧blog,用函数方式实现了log库,并演示了ANSI颜色转义字符的显示
  2. C语言中do…while(0)的妙用
    展示了do…while(0)的必要性
  3. macro-logger
    展示了宏定义实现的log库,包括可配置格式串等
  4. ncnn中的log宏定义
    其实ncnn中的NCNN_LOGE的实现很简洁也基本够用了,推荐一下

Original: https://www.cnblogs.com/zjutzz/p/14317769.html
Author: ChrisZZ
Title: C语言实现简易log工具

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/535648/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 最值得学习阅读的10个C语言开源项目代码

    Webbench Webbench是一个在linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模…

    C语言 2023年5月29日
    054
  • C语言字符串操作总结大全

    转:https://www.cnblogs.com/lidabo/p/5225868.html 1)字符串操作strcpy(p, p1) 复制字符串strncpy(p, p1, n…

    C语言 2023年5月29日
    039
  • 逆向初级-C语言(二)

    2.1.C语言的汇编表示 c语言代码 int plus(int x,int y) { return 0; } void main() { __asm { mov eax,eax }…

    C语言 2023年5月29日
    048
  • C语言:stat,fstat和lstat函数

    这三个函数的功能是一致的,都用于获取文件相关信息,但应用于不同的文件对象。对于函数中给出pathname参数,stat函数返回与此命名文件有关的信息结构,fstat函数获取已在描述…

    C语言 2023年5月29日
    070
  • JavaSE assert断言的学习

    在Java中,assert关键字是从JAVA SE 1.4 引入的,为了避免和老版本的Java代码中使用了assert关键字导致错误,Java在执行的时候默认是不启动断言检查的(这…

    C语言 2023年5月29日
    058
  • C语言实现粒子群算法(PSO)二

    上一回说了基本粒子群算法的实现,并且给出了C语言代码。这一篇主要讲解影响粒子群算法的一个重要参数—w。我们已经说过粒子群算法的核心的两个公式为: Vid(k+1)=wV…

    C语言 2023年5月29日
    051
  • 记C语言浮点数运算处理 “坑” 一则

    看一小段C语言程序: 在你心目中, 变量 I 是怎样的结果? 如果你理所当然地认为是3的话, 那么你就错了~~~ 实际结果应该是2. 为什么? 简而言之, x在内存的值并不是精确的…

    C语言 2023年5月29日
    034
  • 再次实践用c语言来编写webgl

    当年asm.js出来的时候,emscripten这个工具链还不是很好用,不,是很难用。 尝试以后,被一个helloworld 好几兆吓退了。 webassembly 如今已经发育的…

    C语言 2023年5月29日
    048
  • C语言getopt()的8个用法

    概况 例子1 例子2 例子3 例子4 例子5 例子6 例子7 例子8 概况 做 CSAPP 的 CacheLab 的第一个门槛是学习使用 getopt() 函数。它是 Linux …

    C语言 2023年5月29日
    079
  • typedef的用法,C语言typedef详解

    http://c.biancheng.net/view/298.html Original: https://www.cnblogs.com/eustoma/p/10534221….

    C语言 2023年5月29日
    045
  • C语言声明知识体系总结大学霸IT达人

    C语言声明知识体系总结大学霸IT达人 声明(declaration)决定一个或多个标识符的重要性和属性。所声明的标识符可以是对象的名称、函数的名称等。 对象和函数的标识符可以有各式…

    C语言 2023年5月29日
    063
  • 【揭秘】C语言类型转换时发生了什么?

    ID:技术让梦想更伟大作者:李肖遥链接:https://mp.weixin.qq.com/s/ZFf3imVaJgeesuhl1Kn9sQ 在C语言中,数据类型指的是用于声明不同类…

    C语言 2023年5月29日
    052
  • 真的可以,用C语言实现面向对象编程OOP

    ID:技术让梦想更伟大 作者:李肖遥 解释区分一下C语言和OOP 我们经常说C语言是面向过程的,而C++是面向对象的,然而何为面向对象,什么又是面向过程呢?不管怎么样,我们最原始的…

    C语言 2023年5月29日
    053
  • 快速学习C语言一: Hello World

    估计不会写C语言的同学也都听过C语言,从头开始快速学一下吧,以后肯定能用的上。 如果使用过其它类C的语言,如JAVA,C#等,学C的语法应该挺快的。 先快速学习并练习一些基本的语言…

    C语言 2023年5月29日
    070
  • c语言文件处理 (上课用)

    源文件: include //写函数void writetext(FILE *fw){char str[80];gets(str);while(strcmp(str,”…

    C语言 2023年5月29日
    059
  • 遗传算法的C语言实现(一):以非线性函数求极值为例

    以前搞数学建模的时候,研究过(其实也不算是研究,只是大概了解)一些人工智能算法,比如前面已经说过的粒子群算法(PSO),还有著名的遗传算法(GA),模拟退火算法(SA),蚁群算法(…

    C语言 2023年5月29日
    065
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球