优雅的创建参数可变长的文件日志
最近的工作中,为发现一个bug的具体位置,需要自己手动创建一个文件日志,将原本打印在日志进程中的日志,放入一个文件当中。需要这样做的原因是由于这个bug的复现必须重启设备,但是当设备起来的时候,日志进程没抓取到有bug进程的刚启动的几个关键日志。好了废话不多说直接上代码(以C++实现)
1.日志类的实现
#pragma once
#include <string>
#include <cstdarg>
class CFileLog
{
public:
CFileLog():m_FileLocal("D:/CPP/FileLog/FileLog/log2.txt")
{
m_FileDesp = fopen(m_FileLocal.c_str(), "w");
if (m_FileDesp == NULL)
{
printf("create file failed!\n");
}
}
~CFileLog()
{
fclose(m_FileDesp);
}
static CFileLog* GetInstance()
{
if (m_FileLog == NULL)
{
m_FileLog = new CFileLog();
}
return m_FileLog;
}
void ErrLog(const char* format, ...)
{
char szBuf[2048] = { 0 };
va_list arglist;
va_start(arglist, format);
char szBufTemp[2048] = { 0 };
#ifdef _MSC_VER
_vsnprintf_s(szBufTemp, 1023, format, arglist);
sprintf_s(szBuf, "%s\r\n", szBufTemp);
#else
vsnprintf(szBufTemp, 1023, format, arglist);
sprintf(szBuf, "%s\r\n", szBufTemp);
#endif
va_end(arglist);
fprintf(m_FileDesp, "Error:%s", szBuf);
}
void WrnLog(const char* format, ...)
{
char szBuf[2048] = { 0 };
va_list arglist;
va_start(arglist, format);
char szBufTemp[2048] = { 0 };
#ifdef _MSC_VER
_vsnprintf_s(szBufTemp, 1023, format, arglist);
sprintf_s(szBuf, "%s\r\n", szBufTemp);
#else
vsnprintf(szBufTemp, 1023, format, arglist);
sprintf(szBuf, "%s\r\n", szBufTemp);
#endif
va_end(arglist);
fprintf(m_FileDesp, "Warning:%s", szBuf);
}
void InfoLog(const char* format, ...)
{
char szBuf[2048] = { 0 };
va_list arglist;
va_start(arglist, format);
char szBufTemp[2048] = { 0 };
#ifdef _MSC_VER
_vsnprintf_s(szBufTemp, 1023, format, arglist);
sprintf_s(szBuf, "%s\r\n", szBufTemp);
#else
vsnprintf(szBufTemp, 1023, format, arglist);
sprintf(szBuf, "%s\r\n", szBufTemp);
#endif
va_end(arglist);
fprintf(m_FileDesp, "Info:%s", szBuf);
}
void DebugLog(const char* format, ...)
{
char szBuf[2048] = { 0 };
va_list arglist;
va_start(arglist, format);
char szBufTemp[2048] = { 0 };
#ifdef _MSC_VER
_vsnprintf_s(szBufTemp, 1023, format, arglist);
sprintf_s(szBuf, "%s\r\n", szBufTemp);
#else
vsnprintf(szBufTemp, 1023, format, arglist);
sprintf(szBuf, "%s\r\n", szBufTemp);
#endif
va_end(arglist);
fprintf(m_FileDesp, "Debug:%s", szBuf);
}
public:
static CFileLog* m_FileLog; //单例
private:
std::string m_FileLocal; //文件的路径
FILE* m_FileDesp; //打开文件的文件描述符
};
在日志类中,存放私有成员变量m_FileLocal(文件的路径),m_FileDesp(文件的描述符),和类的单例。
在成员变量中声明定义了四种日志,ErrLog(错误日志)、WrnLog(警告日志)、InfoLog(通知日志)和DebugLog(调试日志)。
2.函数参数传递的原理
如何获取省略号指定的参数呢?在函数体内声明一个va_list,然后用va_start函数来获取参数列表的参数,使用完毕后调用va_end()结束。
函数参数是以数据结构:栈的形式存取,从右至左入栈。首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址,举个例子如下:
void func(int x, float y, char z);
那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。所以va_start使得va_list指向可变参数的第一个参数(也就是format)。
_vsnprintf_s的功能就是讲可变参数输出到一个字符数组中。
参数1:char *str [out],把生成的格式化的字符串存放在这里:。
参数2:size_t size [in], str可接受的最大字符数(非字节数,UNICODE一个字符两个字节),防止产生数组越界。
参数3:const char *format [in], 指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序。
参数4:va_list ap [in], va_list变量. va:variable-argument:可变参数。
所以参数会按照格式format都放入字符数组str中,在我们程序中对应的是变量szBufTemp。
3.测试日志程序
#include "Log.h"
CFileLog* CFileLog::m_FileLog = NULL;
#define LOG_WARNING(format,...) CFileLog::GetInstance()->WrnLog(format,##__VA_ARGS__)
#define LOG_ERROR(format,...) CFileLog::GetInstance()->ErrLog(format,##__VA_ARGS__)
#define LOG_INFO(format,...) CFileLog::GetInstance()->InfoLog(format,##__VA_ARGS__)
#define LOG_DEBUG(format,...) CFileLog::GetInstance()->DebugLog(format,##__VA_ARGS__)
int main()
{
int a = 541;
std::string wrn = "WARNINGLOG!";
std::string err = "ERRORLOG!";
std::string deb = "DEBUGLOG!";
std::string info = "INFOLOG!";
LOG_WARNING("This is test log %s %d",wrn.c_str(), __LINE__);
LOG_ERROR("This is test log %s %d %d", err.c_str(), __LINE__, __LINE__);
LOG_INFO("This is test log %s %d %d %d", deb.c_str(), __LINE__, __LINE__, __LINE__);
LOG_DEBUG("This is test log %s %d %d %d %d", info.c_str(), __LINE__, __LINE__, __LINE__, __LINE__);
}
文件内输出如下图所示: