今日分享一下,从某源码中看到这种日志编写方式,很强。可以借鉴。
这个函数调用的日志函数是不一样的,仔细观看:
这几种日志输出函数,背后都调用了相同的调用。
与之对应的区别就是,函数名称的差异取决于调用函数所传入参数的不同。
PLAT_LOG_LEVEL_ERROR
PLAT_LOG_LEVEL_WARN
PLAT_LOG_LEVEL_INFO
这三个宏的不同,代表不同的日志严重等级。
#define aloge(fmt, arg...) GLOG_PRINT(PLAT_LOG_LEVEL_ERROR, fmt, ##arg)
#define alogw(fmt, arg...) GLOG_PRINT(PLAT_LOG_LEVEL_WARN, fmt, ##arg)
#define alogd(fmt, arg...) GLOG_PRINT(PLAT_LOG_LEVEL_INFO, fmt, ##arg)
#define alogv(fmt, arg...)
#define PLAT_LOG_LEVEL_INFO _GLOG_INFO
#define PLAT_LOG_LEVEL_WARN _GLOG_WARN
#define PLAT_LOG_LEVEL_ERROR _GLOG_ERROR
#define PLAT_LOG_LEVEL_FATAL _GLOG_FATAL
宏 GLOG_PRINT 是这样的:
/**
* @brief 定义日志打印宏
*
* 此宏用于在代码中方便地打印不同级别的日志信息。它自动包含文件名、函数名、行号以及自定义的日志级别和格式化字符串。
*
* @param[in] level 日志级别,用于区分日志的重要程度,如DEBUG、INFO、WARN、ERROR等。
* @param[in] fmt 格式化字符串,遵循printf风格,用于构造日志的具体文本内容。
* @param[in] arg... 可变参数列表,与fmt字符串中的占位符对应,支持多个参数输入。
*
* @note
* - 使用##arg确保在参数为空的情况下宏展开正确。
* - 宏通过do-while(0)结构保证其具有块语句的特性,可直接作为单条语句使用而无需额外的大括号包裹。
* - __FILE__、__func__、__LINE__是预处理器宏,分别表示源文件名、函数名、当前行号,提供日志的详细来源信息。
*
* 示例:
* @code
* GLOG_PRINT(DEBUG, "这是一条调试信息,变量x的值为:%d", x);
* @endcode
*/
#define GLOG_PRINT(level, fmt, arg...) \
do { \
log_printf(__FILE__, __func__, __LINE__, level, fmt, ##arg); \
} while (0)
定义宏 GLOG_PRINT
,用于打印日志信息。
宏中 ##arg
语法是C/C++语言的变参宏技巧,处理可变参数。##
的作用是在宏中将可变长度参数的前一个参数和后一个参数连接起来,如果可变长度参数为空,则不产生任何内容。
GLOG_PRINT
宏展开后会调用 log_printf
函数,传递参数 __FILE__
, __func__
, __LINE__
, level
, fmt
以及可变数量的 arg
参数。##arg
确保在参数列表为空时,不会生成多余的逗号或者括号。
代码定义日志打印宏 GLOG_PRINT
,通过变参的方式传递不定数量的参数。
_GLOG_INFO
_GLOG_WARN
_GLOG_ERROR
_GLOG_FATAL
这4个宏配置在这个文件中:
#ifndef LOG_PRINT_H_
#define LOG_PRINT_H_
#define _GLOG_INFO 0
#define _GLOG_WARN 1
#define _GLOG_ERROR 2
#define _GLOG_FATAL 3
#ifdef __cplusplus
extern "C" {
#endif
typedef struct GLogConfig
{
//"log messages go to stderr instead of logfiles"
int FLAGS_logtostderr; // = false; //--logtostderr=1, GLOG_logtostderr=1
//"color messages logged to stderr (if supported by terminal)"
int FLAGS_colorlogtostderr; // = true;
//"log messages at or above this level are copied to stderr in addition to logfiles. This flag obsoletes --alsologtostderr."
int FLAGS_stderrthreshold; // = google::GLOG_WARNING;
//"Messages logged at a lower level than this don't actually get logged anywhere"
int FLAGS_minloglevel; // = google::GLOG_INFO;
//"Buffer log messages logged at this level or lower (-1 means don't buffer; 0 means buffer INFO only;...)"
int FLAGS_logbuflevel; // = -1;
//"Buffer log messages for at most this many seconds"
int FLAGS_logbufsecs; // = 0;
//"approx. maximum log file size (in MB). A value of 0 will be silently overridden to 1."
int FLAGS_max_log_size; // = 25;
//"Stop attempting to log to disk if the disk is full."
int FLAGS_stop_logging_if_full_disk; // = true;
//e.g., "/tmp/log/LOG-"
char LogDir[128]; //e.g., "/tmp/log"
char InfoLogFileNameBase[128]; //e.g., "LOG-"
char LogFileNameExtension[128]; //e.g., "SDV-"
}GLogConfig;
void log_init(const char *program, GLogConfig *pConfig);
void log_quit();
int log_printf(const char *file, const char *func, int line, const int level, const char *format, ...);
#ifdef __cplusplus
}
#endif
#endif
GLOG_PRINT带参数宏调用的函数:
/**
* @brief 打印格式化日志,根据日志级别使用Google日志系统输出
*
* 此函数接收一个文件名、函数名、行号、日志级别和一个可变参数列表,用于生成和打印格式化的日志消息。
* 它首先尝试在固定大小的缓冲区中格式化字符串,如果需要更大的空间,则动态分配内存。
*
* @param file 日志产生时的源代码文件名
* @param func 调用此函数的函数名
* @param line 产生日志的源代码行号
* @param level 日志级别,可以是预定义的_GLOG_INFO, _GLOG_WARN, _GLOG_ERROR, _GLOG_FATAL
* @param format 格式化字符串,类似于printf函数的格式控制符
*
* @return 返回0,目前此函数没有实际返回值
*
* @note
* - 使用vsnprintf进行安全的格式化字符串处理。
* - 如果格式化后的字符串长度超过固定缓冲区大小,将尝试动态分配内存以适应字符串长度。
* - 动态分配的内存会在函数结束前释放。
* - 根据日志级别,使用google::LogMessage流对象将日志输出到相应的日志级别。
*/
int log_printf(const char *file, const char *func, int line, const int level, const char *format, ...)
{
int result = 0;
char ChBuffer[128] = {0}; // 初始化为零的固定大小缓冲区
char *buffer = NULL;
char *p = NULL;
int n;
int size = sizeof(ChBuffer); // 缓冲区初始大小
va_list args; // 变长参数列表指针
// 初始化变长参数列表
va_start(args, format);
// 格式化字符串到固定大小的缓冲区
n = vsnprintf(ChBuffer, size, format, args);
// 结束变长参数列表
va_end(args);
// 如果格式化成功且字符串长度小于固定缓冲区大小,直接使用ChBuffer
if (n > -1 && n < size)
{
buffer = ChBuffer;
}
// 否则,如果需要更多空间,尝试动态分配内存
else if (n > -1)
{
size = n + 1; // 获取精确需要的内存大小
if ((p = (char*)malloc(size)) == NULL)
{
// 内存分配失败,使用固定缓冲区
printf("(f:%s, l:%d) fatal error! n=%d. use previous buffer\n", __FUNCTION__, __LINE__, n);
buffer = ChBuffer;
}
else
{
// 重新格式化到新分配的内存
va_start(args, format);
n = vsnprintf(p, size, format, args);
va_end(args);
// 如果格式化成功,使用新分配的内存
if (n > -1 && n < size)
{
buffer = p;
}
else
{
// 格式化失败,使用固定缓冲区
printf("(f:%s, l:%d) fatal error! n=%d. use previous buffer\n", __FUNCTION__, __LINE__, n);
buffer = ChBuffer;
}
}
}
else
{
// 格式化失败,使用固定缓冲区
printf("(f:%s, l:%d) fatal error! n=%d. use previous buffer\n", __FUNCTION__, __LINE__, n);
buffer = ChBuffer;
}
// 根据日志级别输出日志
switch (level)
{
case _GLOG_INFO:
google::LogMessage(file, line).stream()
<< XPOSTO(60)
<< "<" << func << "> "
//<< XPOSTO(90)
<< buffer;
break;
case _GLOG_WARN:
google::LogMessage(file, line, google::GLOG_WARNING).stream()
<< XPOSTO(60)
<< "<" << func << "> "
//<< XPOSTO(90)
<< buffer;
break;
case _GLOG_ERROR:
google::LogMessage(file, line, google::GLOG_ERROR).stream()
<< XPOSTO(60)
<< "<" << func << "> "
//<< XPOSTO(90)
<< buffer;
break;
case _GLOG_FATAL:
google::LogMessage(file, line, google::GLOG_FATAL).stream()
<< XPOSTO(60)
<< "<" << func << "> "
//<< XPOSTO(90)
<< buffer;
break;
default:
google::LogMessage(file, line).stream()
<< XPOSTO(60)
<< "<" << func << "> "
//<< XPOSTO(90)
<< buffer;
break;
}
// 释放动态分配的内存
if (p)
{
free(p);
p = NULL;
}
return result;
}