C语言使用宏定义控制日志输出
完善的宏定义与日志系统指南
目录
可变参数宏
基本语法
// C99标准可变参数宏
#define PRINT(...) printf(__VA_ARGS__)
// GCC扩展:处理可变参数为空的情况
#define PRINT_SAFE(format, ...) printf(format, ##__VA_ARGS__)
// C++20兼容语法(MSVC支持)
#ifdef _MSC_VER
#define PRINT_MS(format, ...) printf(format, __VA_ARGS__)
#endif
注意事项
- 逗号处理:使用
##__VA_ARGS__可以省略可变参数为空时的逗号 - 参数计数:无法直接获取可变参数的数量
- 类型安全:宏没有类型检查,需要谨慎使用
ANSI颜色代码
标准颜色代码
// 基础颜色
#define ANSI_RESET "\033[0m" // 重置所有属性
#define ANSI_BOLD "\033[1m" // 加粗/高亮
#define ANSI_DIM "\033[2m" // 暗淡
#define ANSI_UNDERLINE "\033[4m" // 下划线
#define ANSI_BLINK "\033[5m" // 闪烁
#define ANSI_REVERSE "\033[7m" // 反色
#define ANSI_HIDDEN "\033[8m" // 隐藏
// 前景色(字体颜色)
#define ANSI_FG_BLACK "\033[30m"
#define ANSI_FG_RED "\033[31m"
#define ANSI_FG_GREEN "\033[32m"
#define ANSI_FG_YELLOW "\033[33m"
#define ANSI_FG_BLUE "\033[34m"
#define ANSI_FG_MAGENTA "\033[35m"
#define ANSI_FG_CYAN "\033[36m"
#define ANSI_FG_WHITE "\033[37m"
#define ANSI_FG_DEFAULT "\033[39m"
// 背景色
#define ANSI_BG_BLACK "\033[40m"
#define ANSI_BG_RED "\033[41m"
#define ANSI_BG_GREEN "\033[42m"
#define ANSI_BG_YELLOW "\033[43m"
#define ANSI_BG_BLUE "\033[44m"
#define ANSI_BG_MAGENTA "\033[45m"
#define ANSI_BG_CYAN "\033[46m"
#define ANSI_BG_WHITE "\033[47m"
#define ANSI_BG_DEFAULT "\033[49m"
// 组合属性(加粗+颜色)
#define ANSI_BOLD_RED "\033[1;31m"
#define ANSI_BOLD_GREEN "\033[1;32m"
#define ANSI_BOLD_YELLOW "\033[1;33m"
#define ANSI_BOLD_BLUE "\033[1;34m"
#define ANSI_BOLD_MAGENTA "\033[1;35m"
#define ANSI_BOLD_CYAN "\033[1;36m"
#define ANSI_BOLD_WHITE "\033[1;37m"
// 256色支持
#define ANSI_256FG(color) "\033[38;5;" #color "m"
#define ANSI_256BG(color) "\033[48;5;" #color "m"
颜色使用注意事项
- 始终重置:使用颜色后必须使用
ANSI_RESET恢复默认 - 终端兼容性:不是所有终端都支持所有颜色特性
- 性能考虑:频繁的颜色切换可能影响输出性能
预定义宏
标准预定义宏
__FILE__ // 当前源文件的完整路径(字符串)
__LINE__ // 当前行号(十进制常量)
__DATE__ // 编译日期("MMM DD YYYY"格式)
__TIME__ // 编译时间("HH:MM:SS"格式)
__STDC__ // 如果编译器符合ANSI C标准则定义为1
__STDC_VERSION__// C标准版本(如199901L表示C99)
// C99新增
__func__ // 当前函数名(字符串)
__VA_ARGS__ // 可变参数宏的参数
// GCC/Clang扩展
__FUNCTION__ // 同__func__(GCC扩展)
__PRETTY_FUNCTION__ // 完整的函数签名(C++中更详细)
// 条件编译常用宏
_WIN32 // Windows系统
__linux__ // Linux系统
__APPLE__ // macOS/iOS系统
__MINGW32__ // MinGW环境
__CYGWIN__ // Cygwin环境
实用技巧
// 创建唯一标识符
#define CONCAT(a, b) a##b
#define UNIQUE_NAME(prefix) CONCAT(prefix, __LINE__)
// 获取字符串化的宏值
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
// 编译时断言(C11 static_assert)
#if __STDC_VERSION__ >= 201112L
#define STATIC_ASSERT(cond, msg) _Static_assert(cond, msg)
#else
#define STATIC_ASSERT(cond, msg) \
typedef char static_assertion_##msg[(cond) ? 1 : -1]
#endif
日志系统设计
日志级别定义
typedef enum {
LOG_LEVEL_TRACE = 0, // 最详细的跟踪信息
LOG_LEVEL_DEBUG = 1, // 调试信息
LOG_LEVEL_INFO = 2, // 一般信息
LOG_LEVEL_WARN = 3, // 警告信息
LOG_LEVEL_ERROR = 4, // 错误信息
LOG_LEVEL_FATAL = 5, // 致命错误
LOG_LEVEL_NONE = 6 // 关闭所有日志
} LogLevel;
// 默认日志级别
#ifndef CURRENT_LOG_LEVEL
#define CURRENT_LOG_LEVEL LOG_LEVEL_INFO
#endif
// 运行时日志级别(如果需要动态调整)
extern LogLevel g_log_level;
日志系统核心宏
1. 基础日志宏
// 带级别检查的通用日志宏
#define LOG_BASE(level, level_str, color, fmt, ...) do { \
if ((level) >= CURRENT_LOG_LEVEL) { \
fprintf(stderr, "[%s] %s%-7s%s %s:%d (%s) - " fmt "\n", \
get_timestamp(), color, level_str, ANSI_RESET, \
__FILE__, __LINE__, __func__, ##__VA_ARGS__); \
fflush(stderr); \
} \
} while(0)
// 为什么使用 do {...} while(0)?
// 1. 确保宏展开后是一个完整的语句
// 2. 可以在 if/else 语句中安全使用
// 3. 可以使用 break 或 continue
2. 具体级别日志宏
#define LOG_TRACE(fmt, ...) \
LOG_BASE(LOG_LEVEL_TRACE, "TRACE", ANSI_FG_CYAN, fmt, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) \
LOG_BASE(LOG_LEVEL_DEBUG, "DEBUG", ANSI_FG_BLUE, fmt, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) \
LOG_BASE(LOG_LEVEL_INFO, "INFO", ANSI_FG_GREEN, fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) \
LOG_BASE(LOG_LEVEL_WARN, "WARN", ANSI_BOLD_YELLOW, fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) \
LOG_BASE(LOG_LEVEL_ERROR, "ERROR", ANSI_BOLD_RED, fmt, ##__VA_ARGS__)
#define LOG_FATAL(fmt, ...) do { \
LOG_BASE(LOG_LEVEL_FATAL, "FATAL", ANSI_BOLD_RED, fmt, ##__VA_ARGS__); \
abort(); \
} while(0)
3. 条件编译日志
#ifdef NDEBUG
#define LOG_DEBUG_ONLY(fmt, ...) // 空宏,编译时移除
#else
#define LOG_DEBUG_ONLY(fmt, ...) LOG_DEBUG(fmt, ##__VA_ARGS__)
#endif
// 函数入口/出口跟踪
#ifdef LOG_TRACE_ENABLED
#define LOG_TRACE_ENTRY() LOG_TRACE("--> %s", __func__)
#define LOG_TRACE_EXIT() LOG_TRACE("<-- %s", __func__)
#else
#define LOG_TRACE_ENTRY()
#define LOG_TRACE_EXIT()
#endif
4. 断言宏
// 调试断言(仅在调试版本有效)
#ifdef NDEBUG
#define ASSERT(expr) ((void)0)
#else
#define ASSERT(expr) do { \
if (!(expr)) { \
LOG_FATAL("Assertion failed: %s", #expr); \
} \
} while(0)
#endif
// 始终有效的断言
#define ENSURE(expr, fmt, ...) do { \
if (!(expr)) { \
LOG_ERROR("Condition failed: %s", #expr); \
LOG_ERROR(fmt, ##__VA_ARGS__); \
exit(EXIT_FAILURE); \
} \
} while(0)
时间戳函数
#include <time.h>
#include <sys/time.h>
// 获取高精度时间戳(毫秒级)
static inline const char* get_timestamp(void) {
static char buffer[32];
struct timeval tv;
struct tm* tm_info;
gettimeofday(&tv, NULL);
tm_info = localtime(&tv.tv_sec);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm_info);
snprintf(buffer + 19, sizeof(buffer) - 19, ".%03ld", tv.tv_usec / 1000);
return buffer;
}
// 简单版本(秒级精度)
static inline const char* get_simple_timestamp(void) {
static char buffer[20];
time_t now = time(NULL);
struct tm* tm_info = localtime(&now);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm_info);
return buffer;
}
完整代码示例
/**
* @file logger.h
* @brief 完整的日志系统实现
* @version 1.0
* @date 2024
*/
#ifndef LOGGER_H
#define LOGGER_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ==================== 颜色定义 ==================== */
#define ANSI_RESET "\033[0m"
#define ANSI_BOLD "\033[1m"
// 前景色
#define ANSI_FG_BLACK "\033[30m"
#define ANSI_FG_RED "\033[31m"
#define ANSI_FG_GREEN "\033[32m"
#define ANSI_FG_YELLOW "\033[33m"
#define ANSI_FG_BLUE "\033[34m"
#define ANSI_FG_MAGENTA "\033[35m"
#define ANSI_FG_CYAN "\033[36m"
#define ANSI_FG_WHITE "\033[37m"
// 背景色
#define ANSI_BG_RED "\033[41m"
#define ANSI_BG_GREEN "\033[42m"
#define ANSI_BG_YELLOW "\033[43m"
#define ANSI_BG_BLUE "\033[44m"
// 组合属性
#define ANSI_BOLD_RED "\033[1;31m"
#define ANSI_BOLD_GREEN "\033[1;32m"
#define ANSI_BOLD_YELLOW "\033[1;33m"
#define ANSI_BOLD_BLUE "\033[1;34m"
#define ANSI_BOLD_CYAN "\033[1;36m"
/* ==================== 日志级别 ==================== */
typedef enum {
LOG_LEVEL_TRACE = 0,
LOG_LEVEL_DEBUG = 1,
LOG_LEVEL_INFO = 2,
LOG_LEVEL_WARN = 3,
LOG_LEVEL_ERROR = 4,
LOG_LEVEL_FATAL = 5,
LOG_LEVEL_NONE = 6
} LogLevel;
// 编译时日志级别控制
#ifndef CURRENT_LOG_LEVEL
#ifdef NDEBUG
#define CURRENT_LOG_LEVEL LOG_LEVEL_INFO
#else
#define CURRENT_LOG_LEVEL LOG_LEVEL_DEBUG
#endif
#endif
/* ==================== 函数声明 ==================== */
const char* get_timestamp(void);
/* ==================== 核心日志宏 ==================== */
#define LOG_BASE(level, level_str, color, fmt, ...) do { \
if ((level) >= CURRENT_LOG_LEVEL) { \
fprintf(stderr, "[%s] %s%-7s%s %s:%d (%s) - " fmt "\n", \
get_timestamp(), color, level_str, ANSI_RESET, \
__FILE__, __LINE__, __func__, ##__VA_ARGS__); \
fflush(stderr); \
} \
} while(0)
/* ==================== 具体级别日志 ==================== */
#define LOG_TRACE(fmt, ...) \
LOG_BASE(LOG_LEVEL_TRACE, "TRACE", ANSI_FG_CYAN, fmt, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) \
LOG_BASE(LOG_LEVEL_DEBUG, "DEBUG", ANSI_FG_BLUE, fmt, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) \
LOG_BASE(LOG_LEVEL_INFO, "INFO", ANSI_FG_GREEN, fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) \
LOG_BASE(LOG_LEVEL_WARN, "WARN", ANSI_BOLD_YELLOW, fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) \
LOG_BASE(LOG_LEVEL_ERROR, "ERROR", ANSI_BOLD_RED, fmt, ##__VA_ARGS__)
#define LOG_FATAL(fmt, ...) do { \
LOG_BASE(LOG_LEVEL_FATAL, "FATAL", ANSI_BOLD_RED, fmt, ##__VA_ARGS__); \
abort(); \
} while(0)
/* ==================== 条件日志 ==================== */
#ifdef LOG_TRACE_ENABLED
#define LOG_TRACE_ENTRY() LOG_TRACE("--> %s", __func__)
#define LOG_TRACE_EXIT() LOG_TRACE("<-- %s", __func__)
#define LOG_TRACE_MSG(msg) LOG_TRACE("%s: %s", __func__, msg)
#else
#define LOG_TRACE_ENTRY()
#define LOG_TRACE_EXIT()
#define LOG_TRACE_MSG(msg)
#endif
/* ==================== 断言宏 ==================== */
// 调试断言
#ifdef NDEBUG
#define ASSERT(expr) ((void)0)
#define ASSERT_MSG(expr, msg) ((void)0)
#else
#define ASSERT(expr) do { \
if (!(expr)) { \
LOG_FATAL("Assertion failed: %s (%s:%d)", \
#expr, __FILE__, __LINE__); \
} \
} while(0)
#define ASSERT_MSG(expr, msg, ...) do { \
if (!(expr)) { \
LOG_FATAL("Assertion failed: %s", #expr); \
LOG_FATAL(msg, ##__VA_ARGS__); \
} \
} while(0)
#endif
// 生产环境断言(始终有效)
#define ENSURE(expr, fmt, ...) do { \
if (!(expr)) { \
LOG_ERROR("Condition failed: %s", #expr); \
LOG_ERROR(fmt, ##__VA_ARGS__); \
exit(EXIT_FAILURE); \
} \
} while(0)
/* ==================== 时间戳函数实现 ==================== */
static inline const char* get_timestamp(void) {
static __thread char buffer[32]; // 线程局部存储
struct timeval tv;
struct tm tm_info;
gettimeofday(&tv, NULL);
localtime_r(&tv.tv_sec, &tm_info);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_info);
snprintf(buffer + 19, sizeof(buffer) - 19, ".%03ld", tv.tv_usec / 1000);
return buffer;
}
/* ==================== 实用宏 ==================== */
// 字符串化宏
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
// 连接宏
#define CONCAT(a, b) a##b
#define UNIQUE_NAME(prefix) CONCAT(prefix, __LINE__)
// 编译时检查
#if __STDC_VERSION__ >= 201112L
#define STATIC_ASSERT(cond, msg) _Static_assert(cond, msg)
#else
#define STATIC_ASSERT(cond, msg) \
typedef char UNIQUE_NAME(static_assert_)[(cond) ? 1 : -1]
#endif
#ifdef __cplusplus
}
#endif
#endif /* LOGGER_H */
/**
* @file logger_demo.c
* @brief 日志系统使用示例
*/
#include "logger.h"
#include <string.h>
#include <errno.h>
/* ==================== 示例函数 ==================== */
void process_file(const char* filename) {
LOG_TRACE_ENTRY();
LOG_INFO("开始处理文件: %s", filename);
FILE* fp = fopen(filename, "r");
if (!fp) {
LOG_ERROR("无法打开文件 %s: %s", filename, strerror(errno));
return;
}
// 模拟文件处理
for (int i = 0; i < 3; i++) {
LOG_DEBUG("处理第 %d 行数据", i + 1);
// 模拟处理延迟
}
fclose(fp);
LOG_INFO("文件处理完成: %s", filename);
LOG_TRACE_EXIT();
}
int validate_input(int value) {
LOG_TRACE_MSG("验证输入参数");
if (value < 0) {
LOG_WARN("输入值为负数: %d", value);
return 0;
}
if (value > 100) {
LOG_ERROR("输入值超出范围: %d (最大值: 100)", value);
return 0;
}
LOG_DEBUG("输入验证通过: %d", value);
return 1;
}
void critical_operation(void) {
LOG_INFO("执行关键操作");
// 模拟失败情况
int success = 0;
ENSURE(success, "关键操作必须成功");
LOG_INFO("关键操作完成");
}
/* ==================== 主函数 ==================== */
int main(int argc, char** argv) {
LOG_INFO("程序启动");
LOG_INFO("命令行参数: %d 个", argc);
// 演示不同日志级别
printf("=== 日志级别演示 ===\n");
LOG_TRACE("跟踪信息");
LOG_DEBUG("调试信息");
LOG_INFO("一般信息");
LOG_WARN("警告信息");
LOG_ERROR("错误信息");
printf("\n=== 函数调用演示 ===\n");
// 演示函数调用
process_file("test.txt");
printf("\n=== 输入验证演示 ===\n");
validate_input(-5);
validate_input(50);
validate_input(150);
printf("\n=== 断言演示 ===\n");
// 调试断言(只在DEBUG版本有效)
int x = 10;
ASSERT(x > 0);
// 生产环境断言(始终有效)
ENSURE(argc > 0, "参数数量异常");
printf("\n=== 条件编译演示 ===\n");
LOG_TRACE_ENTRY();
LOG_TRACE_MSG("条件编译测试");
LOG_TRACE_EXIT();
printf("\n=== 模拟致命错误 ===\n");
// LOG_FATAL("发生致命错误,程序将终止");
LOG_INFO("程序正常结束");
return 0;
}
高级特性
1. 线程安全版本
#include <pthread.h>
// 线程安全的日志宏
#define LOG_THREAD_SAFE(level, level_str, color, fmt, ...) do { \
static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; \
if ((level) >= CURRENT_LOG_LEVEL) { \
pthread_mutex_lock(&log_mutex); \
fprintf(stderr, "[%s][%lu] %s%-7s%s %s:%d - " fmt "\n", \
get_timestamp(), (unsigned long)pthread_self(), \
color, level_str, ANSI_RESET, \
__FILE__, __LINE__, ##__VA_ARGS__); \
fflush(stderr); \
pthread_mutex_unlock(&log_mutex); \
} \
} while(0)
2. 性能优化版本
// 使用宏避免函数调用开销
#define LOG_FAST(level, level_str, color, fmt, ...) do { \
if ((level) >= CURRENT_LOG_LEVEL) { \
struct timeval tv; \
struct tm tm_info; \
char timestamp[32]; \
\
gettimeofday(&tv, NULL); \
localtime_r(&tv.tv_sec, &tm_info); \
strftime(timestamp, sizeof(timestamp), \
"%H:%M:%S", &tm_info); \
\
fprintf(stderr, "[%s.%03ld] %s%-7s%s " fmt "\n", \
timestamp, tv.tv_usec / 1000, \
color, level_str, ANSI_RESET, ##__VA_ARGS__); \
} \
} while(0)
3. 日志输出到文件
void log_to_file(const char* level_str, const char* color,
const char* file, int line, const char* func,
const char* fmt, ...) {
static FILE* log_file = NULL;
if (!log_file) {
log_file = fopen("app.log", "a");
if (!log_file) return;
}
va_list args;
va_start(args, fmt);
fprintf(log_file, "[%s] %-7s %s:%d (%s) - ",
get_timestamp(), level_str, file, line, func);
vfprintf(log_file, fmt, args);
fprintf(log_file, "\n");
fflush(log_file);
va_end(args);
}
#define LOG_FILE(level, level_str, fmt, ...) do { \
if ((level) >= CURRENT_LOG_LEVEL) { \
log_to_file(level_str, "", __FILE__, __LINE__, \
__func__, fmt, ##__VA_ARGS__); \
} \
} while(0)
最佳实践
1. 宏命名规范
- 使用大写字母和下划线
- 明确区分不同类别的宏
- 避免与标准库冲突
2. 错误处理
// 良好的错误日志
LOG_ERROR("函数 %s 失败: %s (错误码: %d)",
"fopen", strerror(errno), errno);
// 不好的错误日志
LOG_ERROR("打开文件失败"); // 信息不足
3. 性能考虑
- 生产环境适当提高日志级别
- 避免在循环中记录过多日志
- 考虑使用条件编译移除调试日志
4. 可移植性
// 跨平台颜色支持
#ifdef _WIN32
#include <windows.h>
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
void enable_ansi_colors(void) {
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
GetConsoleMode(hOut, &dwMode);
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(hOut, dwMode);
}
#endif
5. 安全建议
- 避免在日志中输出敏感信息
- 注意格式化字符串漏洞
- 控制日志文件大小和轮转
总结
一个完整的日志系统应该包含以下特性:
- 多级别日志:支持从TRACE到FATAL的不同级别
- 条件编译:生产环境可以移除调试信息
- 颜色输出:提高可读性
- 时间戳:精确的时间信息
- 源位置:文件名、行号、函数名
- 线程安全:多线程环境下的安全输出
- 性能考虑:最小化性能影响
- 可配置性:运行时或编译时控制日志级别
通过合理使用宏和预定义符号,可以构建一个既强大又灵活的日志系统,大大提高代码的可维护性和调试效率。
扩展阅读
本文来自博客园,作者:奈何清风,转载请注明原文链接:https://www.cnblogs.com/Buling-/articles/19416889
浙公网安备 33010602011771号