在嵌入式开发和Android底层开发中,日志系统是调试和问题排查的核心工具。标准的printf函数虽然简单易用,但在实际项目中有几个明显缺陷:
我在开发STM32和Android HAL层时,发现一个精心设计的日志宏可以提升至少30%的调试效率。下面分享一套经过实战检验的C语言日志宏实现方案。
C99标准引入了__VA_ARGS__宏,允许宏接受可变数量的参数。这是构建灵活日志宏的基础:
c复制#define LOG(format, ...) printf(format, __VA_ARGS__)
使用时:
c复制LOG("Value: %d, Name: %s", 42, "test");
// 展开为:printf("Value: %d, Name: %s\n", 42, "test");
在嵌入式系统中,手动添加\n容易遗漏。可以通过字符串字面量连接特性自动添加换行:
c复制#define LOG(format, ...) printf(format "\n", __VA_ARGS__)
注意:这种写法要求format必须是字符串字面量,不能是字符指针变量
一个专业的日志系统应该包含多个级别:
c复制typedef enum {
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARNING,
LOG_LEVEL_ERROR,
LOG_LEVEL_FATAL
} LogLevel;
c复制#define LOG(level, format, ...) \
do { \
if (level >= CURRENT_LOG_LEVEL) { \
printf("[%s] %s:%d " format "\n", \
log_level_strings[level], __FILE__, __LINE__, ##__VA_ARGS__); \
} \
} while (0)
使用示例:
c复制#define CURRENT_LOG_LEVEL LOG_LEVEL_DEBUG
LOG(LOG_LEVEL_DEBUG, "Sensor value: %f", sensor_read());
为每个日志等级创建专用宏,提高可读性:
c复制#define LOGD(format, ...) LOG(LOG_LEVEL_DEBUG, format, ##__VA_ARGS__)
#define LOGI(format, ...) LOG(LOG_LEVEL_INFO, format, ##__VA_ARGS__)
#define LOGW(format, ...) LOG(LOG_LEVEL_WARNING, format, ##__VA_ARGS__)
#define LOGE(format, ...) LOG(LOG_LEVEL_ERROR, format, ##__VA_ARGS__)
#define LOGF(format, ...) LOG(LOG_LEVEL_FATAL, format, ##__VA_ARGS__)
通过预编译宏实现完全移除不需要的日志代码:
c复制#ifdef DISABLE_DEBUG_LOGS
#define LOGD(format, ...)
#endif
在支持ANSI颜色的终端中,可以使用颜色区分日志级别:
c复制#define LOG_COLOR_RED "\x1B[31m"
#define LOG_COLOR_GREEN "\x1B[32m"
#define LOG_COLOR_YELLOW "\x1B[33m"
#define LOG_COLOR_RESET "\x1B[0m"
#define LOG(level, format, ...) \
printf("%s[%s]%s %s:%d " format "\n", \
log_colors[level], log_level_strings[level], \
LOG_COLOR_RESET, __FILE__, __LINE__, ##__VA_ARGS__)
在Android NDK开发中,建议使用__android_log_print替代printf:
c复制#ifdef ANDROID
#include <android/log.h>
#define LOG(level, format, ...) \
__android_log_print(android_log_levels[level], LOG_TAG, \
"%s:%d " format, __FILE__, __LINE__, ##__VA_ARGS__)
#endif
当可变参数为空时,传统的__VA_ARGS__会导致语法错误。GCC/Clang提供了##__VA_ARGS__扩展:
c复制// 正确写法
#define LOG(format, ...) printf(format "\n", ##__VA_ARGS__)
// 可以这样调用
LOG("No parameters");
__FILE__会存储完整路径字符串,可以考虑定义__FILENAME__宏:c复制#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
在多线程环境中,printf不是线程安全的。可以:
下面是一个经过生产环境验证的完整实现:
c复制#include <stdio.h>
#include <string.h>
typedef enum {
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARNING,
LOG_LEVEL_ERROR,
LOG_LEVEL_FATAL,
LOG_LEVEL_NONE
} LogLevel;
static const char* log_level_strings[] = {
"DEBUG",
"INFO",
"WARN",
"ERROR",
"FATAL"
};
static const char* log_colors[] = {
"\x1B[36m", // DEBUG - Cyan
"\x1B[32m", // INFO - Green
"\x1B[33m", // WARNING - Yellow
"\x1B[31m", // ERROR - Red
"\x1B[35m" // FATAL - Magenta
};
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define LOG(level, format, ...) \
do { \
if (level >= CURRENT_LOG_LEVEL) { \
printf("%s%-7s\x1B[0m \x1B[90m%s:%d\x1B[0m " format "\n", \
log_colors[level], log_level_strings[level], \
__FILENAME__, __LINE__, ##__VA_ARGS__); \
} \
} while (0)
#define LOGD(format, ...) LOG(LOG_LEVEL_DEBUG, format, ##__VA_ARGS__)
#define LOGI(format, ...) LOG(LOG_LEVEL_INFO, format, ##__VA_ARGS__)
#define LOGW(format, ...) LOG(LOG_LEVEL_WARNING, format, ##__VA_ARGS__)
#define LOGE(format, ...) LOG(LOG_LEVEL_ERROR, format, ##__VA_ARGS__)
#define LOGF(format, ...) LOG(LOG_LEVEL_FATAL, format, ##__VA_ARGS__)
// 全局日志级别设置
LogLevel CURRENT_LOG_LEVEL = LOG_LEVEL_DEBUG;
在生产环境中,通常需要将日志写入文件:
c复制void log_to_file(const char* message) {
static FILE* log_file = NULL;
if (!log_file) {
log_file = fopen("app.log", "a");
if (!log_file) return;
}
fprintf(log_file, "%s", message);
fflush(log_file);
}
防止日志文件过大:
c复制#define MAX_LOG_SIZE (10 * 1024 * 1024) // 10MB
void check_log_rotation() {
struct stat st;
if (stat("app.log", &st) == 0 && st.st_size > MAX_LOG_SIZE) {
rename("app.log", "app.log.old");
}
}
大型项目中可能需要按模块控制日志:
c复制typedef enum {
LOG_MODULE_CORE,
LOG_MODULE_NETWORK,
LOG_MODULE_UI,
// ...
} LogModule;
#define LOG_MODULE_FILTER 0xFF // 默认所有模块
#define LOG_MODULE(module, level, format, ...) \
do { \
if ((module & LOG_MODULE_FILTER) && (level >= CURRENT_LOG_LEVEL)) { \
printf("[%s] " format "\n", log_level_strings[level], ##__VA_ARGS__); \
} \
} while (0)
在嵌入式开发中,一个设计良好的日志系统可以显著提高调试效率。这套宏定义方案经过多个商业项目的验证,平衡了功能性和性能开销。根据项目需求,可以进一步扩展添加线程ID、时间戳、日志压缩等功能。