如何为C语言自定义日志库添加可变参数格式化打印功能
改造方案
要实现格式化可变参数打印,我们需要借助C语言标准库的<stdarg.h>处理可变参数,同时修改日志宏支持可变参数传递。
1. 修改 cd_log.h
#ifndef CONQUER_DOCKER_CD_LOG_H #define CONQUER_DOCKER_CD_LOG_H #include <stdarg.h> #define CD_CURRENT_LOG_LEVEL CD_LOG_INFO_LEVEL // 宏改造支持可变参数,##__VA_ARGS__兼容无额外参数的场景 #define CD_LOG_DEBUG(fmt, ...) cd_log(&CD_LOG_DEBUG_LEVEL, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) #define CD_LOG_INFO(fmt, ...) cd_log(&CD_LOG_INFO_LEVEL, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) #define CD_LOG_WARNING(fmt, ...) cd_log(&CD_LOG_WARNING_LEVEL, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) #define CD_LOG_ERROR(fmt, ...) cd_log(&CD_LOG_ERROR_LEVEL, __FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) /** * log level */ typedef struct CDLogLevel { const char level; /** * level name */ const char *name; } CDLogLevel; /** * log level */ extern const CDLogLevel CD_LOG_DEBUG_LEVEL; extern const CDLogLevel CD_LOG_INFO_LEVEL; extern const CDLogLevel CD_LOG_WARNING_LEVEL; extern const CDLogLevel CD_LOG_ERROR_LEVEL; /** * print log */ void cd_log( const CDLogLevel *logLevel, const char *fileName, const char *functionName, int line, const char *fmt, ... ); #endif //CONQUER_DOCKER_CD_LOG_H
2. 修改 cd_log.c
#include "include/cd_log.h" #include <stdio.h> #include <time.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> const CDLogLevel CD_LOG_DEBUG_LEVEL = {0, "DEBUG"}; const CDLogLevel CD_LOG_INFO_LEVEL = {1, "INFO"}; const CDLogLevel CD_LOG_WARNING_LEVEL = {2, "WARNING"}; const CDLogLevel CD_LOG_ERROR_LEVEL = {3, "ERROR"}; static const char *coloring(const CDLogLevel *logLevel) { size_t strLength = strlen(logLevel->name); size_t newStrLen = strLength + 16; char *newStr = malloc(newStrLen); memset(newStr, 0, newStrLen); if (logLevel->level == CD_LOG_DEBUG_LEVEL.level) { snprintf(newStr, newStrLen, "\033[34m%s\033[0m", logLevel->name); } else if (logLevel->level == CD_LOG_INFO_LEVEL.level) { snprintf(newStr, newStrLen, "\033[32m%s\033[0m", logLevel->name); } else if (logLevel->level == CD_LOG_WARNING_LEVEL.level) { snprintf(newStr, newStrLen, "\033[33m%s\033[0m", logLevel->name); } else if (logLevel->level == CD_LOG_ERROR_LEVEL.level) { snprintf(newStr, newStrLen, "\033[31m%s\033[0m", logLevel->name); } else { free(newStr); return logLevel->name; } return newStr; } void cd_log(const CDLogLevel *logLevel, const char *fileName, const char *functionName, int line, const char *fmt, ...) { if (logLevel->level < CD_CURRENT_LOG_LEVEL.level) { return; } // 处理可变参数,动态计算格式化后字符串长度避免溢出 va_list ap; va_start(ap, fmt); int str_len = vsnprintf(NULL, 0, fmt, ap); va_end(ap); if (str_len < 0) return; char *message_buf = malloc(str_len + 1); if (!message_buf) return; va_start(ap, fmt); vsnprintf(message_buf, str_len + 1, fmt, ap); va_end(ap); time_t currentTime = time(NULL); char timerStr[20]; strftime(timerStr, 20, "%Y-%m-%d %H:%M:%S", localtime(¤tTime)); if (isatty(STDOUT_FILENO)) { const char *levelStr = coloring(logLevel); fprintf(stdout, "%s [%-15s] %-80s %s %5d: %s\n", timerStr, levelStr, fileName, functionName, line, message_buf); free((void *) levelStr); } else { fprintf(stdout, "%s [%-7s] %-80s %s %5d: %s\n", timerStr, logLevel->name, fileName, functionName, line, message_buf); } free(message_buf); }
3. 调用示例
直接按你期望的方式调用即可:
// 无额外参数场景兼容 CD_LOG_INFO("Hello World"); // 多参数格式化场景 CD_LOG_INFO("value01: %s, value02: %s, value03: %d" , "demo01", "demo2", 100);
关键说明
- 宏定义中添加的
##__VA_ARGS__是GCC/Clang等主流编译器支持的语法,用于处理无额外参数时多余的逗号问题,兼容单字符串打印场景 - 采用两次
vsnprintf的方式动态计算字符串长度,避免固定缓冲区溢出风险 - 动态分配的内存都会在打印完成后释放,不会产生内存泄漏
内容的提问来源于stack exchange,提问作者aszswaz
相关产品推荐
相关产品推荐

