Buling-

导航

C语言使用宏定义控制日志输出

完善的宏定义与日志系统指南

目录

  1. 可变参数宏
  2. ANSI颜色代码
  3. 预定义宏
  4. 日志系统设计
  5. 完整代码示例
  6. 高级特性
  7. 最佳实践

可变参数宏

基本语法

// 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

注意事项

  1. 逗号处理:使用##__VA_ARGS__可以省略可变参数为空时的逗号
  2. 参数计数:无法直接获取可变参数的数量
  3. 类型安全:宏没有类型检查,需要谨慎使用

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"

颜色使用注意事项

  1. 始终重置:使用颜色后必须使用ANSI_RESET恢复默认
  2. 终端兼容性:不是所有终端都支持所有颜色特性
  3. 性能考虑:频繁的颜色切换可能影响输出性能

预定义宏

标准预定义宏

__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. 安全建议

  • 避免在日志中输出敏感信息
  • 注意格式化字符串漏洞
  • 控制日志文件大小和轮转

总结

一个完整的日志系统应该包含以下特性:

  1. 多级别日志:支持从TRACE到FATAL的不同级别
  2. 条件编译:生产环境可以移除调试信息
  3. 颜色输出:提高可读性
  4. 时间戳:精确的时间信息
  5. 源位置:文件名、行号、函数名
  6. 线程安全:多线程环境下的安全输出
  7. 性能考虑:最小化性能影响
  8. 可配置性:运行时或编译时控制日志级别

通过合理使用宏和预定义符号,可以构建一个既强大又灵活的日志系统,大大提高代码的可维护性和调试效率。

扩展阅读

  1. ANSI Escape Codes
  2. C11 Standard
  3. GCC Macro Documentation
  4. Logging Best Practices

posted on 2025-12-29 17:01  奈何清风  阅读(108)  评论(0)    收藏  举报