这是本人实现的一个简单的软件调试日志。论功能肯定不能和glog、log4cplus等日志相提并论,但是我也觉得这个两个日志库太强大,也有点庞大,个人想法。
实现的这个日志只包含一个头文件和一个cpp文件。
日志使用平台为windows,主要测试环境为win32和MFC。
是将日志直接输出到标准输出stdout和stderr。通过宏实现可变参数以及输出日志时间和文件名函数名行号。要输出什么内容可以通过_PRINTF_LOCATION_自行配置。当然很多是将日志写入文件中的,这里通过重导向标准输出流(__iob_func()[1]、__iob_func()[2])的方式输出到文件,重导向了标准输出流有个意外的收获,就是有时同时把程序中一些其他库的错误输出也保存了下来。通过_fsopen()函数实现日志文件的读共享写独占。通过_snprintf_s()格式化字符串在信息长度超过buff大小时会自动截断而不会像其他格式化函数会崩溃。
这里贴出部分源码
namespace NamespaceKun
{
// 创建并打开日志文件,当文件名为空,表示关闭对应的日志文件,两个日志文件名最好不相同
// infoOutFileName替换的是标准输出流stdout
// errOutFileName替换的标准错误输出流stderr
bool LogInit( const char *infoOutFileName, const char * errOutFileName,
const char * infoOutFileMode = "w+", const char * errOutFileMode = "w+" );
//打开控制台
void OpenConsole();
};
//信息输出
#define _INFO_PRINTF_
//警告输出
#define _WARNING_PRINTF_
//错误输出
#define _ERROR_PRINTF_
//输出方式:
//0: //是否显示输出的位置,无附加信息
//1://是否显示输出的位置,行号
//2://是否显示输出的位置,函数,行号
//3://是否显示输出的位置,包括文件,函数,行号
#ifdef _DEBUG
#define _PRINTF_LOCATION_ 0
#else
#define _PRINTF_LOCATION_ 3
#endif
//----------------------------------------------------------------------------------
//是否显示输出的位置
#if _PRINTF_LOCATION_ == 0
#define _FPRINTF_PARAMETERS_ "%s %s %s\n",datetimebuff,pszLevel,szOutBuff
#elif _PRINTF_LOCATION_ == 1
#define _FPRINTF_PARAMETERS_ "%s %s %s\tLine:%d\n",datetimebuff,pszLevel,szOutBuff,__LINE__
#elif _PRINTF_LOCATION_ == 2
#define _FPRINTF_PARAMETERS_ "%s %s %s\tFunction:%s, Line:%d\n",datetimebuff,pszLevel,szOutBuff,__FUNCTION__,__LINE__
#elif _PRINTF_LOCATION_ == 3
#define _FPRINTF_PARAMETERS_ "%s %s %s\tFilePath:%s, Function:%s, Line:%d\n",datetimebuff,pszLevel,szOutBuff,__FILE__,__FUNCTION__,__LINE__
#else
#define _FPRINTF_PARAMETERS_ "%s %s %-500s\tFilePath:%s, Function:%s, Line:%d\n",datetimebuff,pszLevel,szOutBuff,__FILE__,__FUNCTION__,__LINE__
#endif
//----------------------------------------------------------------------------------
//构造输出字数串
#define _PRINTF_FORMATE_CONTENT_(szLevel, format, ...) char datetimebuff[32] = {0};\
const char *pszLevel = szLevel;\
time_t nowtime = time(NULL);\
tm tmdatetime;\
localtime_s(&tmdatetime, &nowtime);\
strftime(datetimebuff,sizeof(datetimebuff),"%Y-%m-%d %H:%M:%S",&tmdatetime);\
char szOutBuff[10240] = {0};\
_snprintf_s(szOutBuff, sizeof(szOutBuff)-1, _TRUNCATE, format, ##__VA_ARGS__);
//调试输出----------------------------------------------------------------------------------
#ifdef _INFO_PRINTF_
#define InfoPrintf(format, ...); {\
_PRINTF_FORMATE_CONTENT_("INFO ", format, ##__VA_ARGS__) \
fprintf(stdout, _FPRINTF_PARAMETERS_);\
fflush(stdout);\
};
#else
#define InfoPrintf(...); do{ }while(0);
#endif
//----------------------------------------------------------------------------------
//调试输出----------------------------------------------------------------------------------
#ifdef _WARNING_PRINTF_
#define WarnPrintf(format, ...); {\
_PRINTF_FORMATE_CONTENT_("WARN ", format, ##__VA_ARGS__) \
fprintf(stdout, _FPRINTF_PARAMETERS_);\
fflush(stdout);\
};
#else
#define WarnPrintf(...); do{ }while(0);
#endif
//----------------------------------------------------------------------------------
//错误输出----------------------------------------------------------------------------------
#ifdef _ERROR_PRINTF_
#define ErrPrintf(format, ...); {\
_PRINTF_FORMATE_CONTENT_("ERROR", format, ##__VA_ARGS__) \
fprintf(stderr, _FPRINTF_PARAMETERS_);\
fflush(stderr);\
};
#else
#define ErrPrintf(...); do{ }while(0);
#endif
//----------------------------------------------------------------------------------
打开控制台和重导向日志到文件函数的实现
namespace NamespaceKun
{
//
bool LogInit( const char *infoOutFileName, const char * errOutFileName,
const char * infoOutFileMode, const char * errOutFileMode )
{
//保存stdout,stderr句柄
static FILE stdoutHandle = __iob_func()[1];
static FILE stderrHandle = __iob_func()[2];
static FILE * logInfoFile = NULL;
static FILE * logErrFile = NULL;
bool bRet = true; //返回值
if ( NULL != logInfoFile )
{//关闭文件句柄
if (logErrFile == logInfoFile)
{//上次打开的是同一个文件
logErrFile = NULL;
__iob_func()[2] = stderrHandle;
}
fclose(logInfoFile);
logInfoFile = NULL;
__iob_func()[1] = stdoutHandle;
}
if ( NULL != logErrFile )
{//关闭文件句柄
fclose(logErrFile);
logErrFile = NULL;
__iob_func()[2] = stderrHandle;
}
//替换标准输出流
if ( infoOutFileName )
{
//打开文件
logInfoFile = _fsopen(infoOutFileName, infoOutFileMode, _SH_DENYWR);
if (NULL != logInfoFile)
{
__iob_func()[1] = *logInfoFile; //stdout
}
else
{
char errbuff[1024] = {0};
strerror_s(errbuff, sizeof(errbuff), errno);
fprintf(stderr, "%s\n", errbuff);
bRet = false;
}
}
//错误输出流
if (errOutFileName)
{
if ( 0 == strcmp(errOutFileName, infoOutFileName) )
{//当错误和信息文件是同一个文件,则错误输出也使用信息输出的句柄
if (NULL != logInfoFile)
{
__iob_func()[2] = *logInfoFile; //stderr
logErrFile = logInfoFile;
}
}
else
{
//打开文件
logErrFile = _fsopen(errOutFileName, errOutFileMode, _SH_DENYWR);
if (NULL != logErrFile)
{
__iob_func()[2] = *logErrFile; //stderr
}
else
{
char errbuff[1024] = {0};
strerror_s(errbuff, sizeof(errbuff), errno);
fprintf(stderr, "%s\n", errbuff);
bRet = false;
}
}
}
return bRet;
}
void OpenConsole()
{//开启控制台
BOOL re = ::AllocConsole();
FILE *consoleStdout, *consoleStderr, *consoleStdin;
freopen_s(&consoleStdout,"CONOUT$","w+t", stdout);
freopen_s(&consoleStderr, "CONOUT$","w+t", stderr);
freopen_s(&consoleStdin, "CONIN$", "r+t", stdin);
}
};//namespace NamespaceKun
//例子
#ifdef _DEBUG
NamespaceKun::OpenConsole(); //debug模式下MFC程序打开控制台显示
#else
//release模式下直接将日志输出到文件
time_t nowtime = time(NULL);
tm tmdatetime;
localtime_s(&tmdatetime, &nowtime);
char szInfoLogNameBuff[256] = {0};
char szErrLogNameBuff[256] = {0};
strftime(szInfoLogNameBuff,sizeof(szInfoLogNameBuff),"%Y-%m-%d_%H:%M:%S日志INFO.txt",&tmdatetime);
strftime(szErrLogNameBuff,sizeof(szErrLogNameBuff),"%Y-%m-%d_%H:%M:%S日志ERR.txt",&tmdatetime);
NamespaceKun::LogInit(szInfoLogNameBuff, szErrLogNameBuff);//stdout和stderr可以输出到同一个文件
#endif
InfoPrintf("日志普通输出,数字:%d", 12345678);
WarnPrintf("日志警告输出,数字:%d", 12345678);
ErrPrintf("日志错误信息输出,数字:%d", 12345678);
效果如下图:
实现比较简单通道也造成了一些问题。比如长时间运行后会有造成日志文件过大。