目录
1、日志文件生成控制策略
2、日志文件清理机制说明
3、清理日志的代码实现
3.1 获取单个文件的修改时间
3.2 遍历文件夹中所有的日志文件,清理若干天之前的文件
为了方便排查软件运行过程中的问题,很多软件在运行过程中会将运行日志写到指定位置的日志文件中,日志记录已经成为大多数软件的一个标配。随着软件的长时间持续运行,日志文件会越来越多,会占用展会用越来越多的磁盘空间。一般情况下,我们只需要出问题时间点附近的日志,若干天前的运行日志可能就不再需要了。这就需要在软件中增加一个日志清理机制,定时去清除一些老旧的日志文件。本文就来详细地讲述一种清理日志文件的思路和方法。
一般情况下,不能将所有生成的日志都写到一个文件中,如果将每天生成的日志都写到同一个文件中,文件会变得越来越大,越来越臃肿,以至于打开文件时都需要很长时间。所以我们在生成日志时,都会做一些控制策略,在需要的时候切换到新的文件去写(切文件)。
切文件的好处,一是每个文件不会太大,二是方便老旧日志文件的清理(我们可以清理指定时间之前的所有日志文件)。
比如为了方便查看,有的软件每天都会按照当天的日期重新生成一个日志文件,文件的名称中会标记上当天的日期,如下:
这个出问题时,我们就可以查看对应日期的日志去分析就好了。
再比如有的软件或者模块,他们的日志控制机制是不同的。比如他们在生成日志时,日志文件名称并不会带上日期,他们会控制每个日志文件的大小,当文件达到上限值(比如50MB)时,会自动切换文件,如下所示:
虽然文件名称中没有日期,但文件中的每条日志都有带上年月日时分秒的时间戳,甚至精确到毫秒。出问题时,我们通过出问题的时间点,到这些文件中找到对应时间点的文件,去查看去分析就可以了。
随着程序的持续运行,日志文件会越来越多,我们要及时地将不要的老旧日志给清理掉,将占用的磁盘给及时地释放掉。此处的清理机制是建立在日志文件自动切换到(自动切换到新的文件中)的基础上的。
我们可以遍历日志路径下的所有文件,获取这些文件的最后修改时间,与当前时间点做比较,将若干天之前的日志文件都删除掉,只保留最近若干天的日志。当然,这也要求我们出问题时要及时地去拿日志文件,时间拖久了,日志可能就被删除了。
我们可以在软件启动时开启一个新的线程,去做日志的检测和清理工作,完成后就退出线程。那对于电脑一直没关机、软件一直运行的场景呢?我们可以开启一个定时,如果持续运行了一段时间后,自动开启新的线程去做日志检测与清理工作。
我们先调用系统API函数FindFirstFile获取日志文件的最后修改时间,然后调用API函数FileTimeToLocalFileTime将修改时间转换成本地文件时间,接着再调用FileTimeToSystemTime函数将时间转成SYSTEMTIME结构体时间,然后再转成64位整型时间。相关代码如下所示:
// 获取日志文件的最后修改时间
// 参数:strFilePath[in], sysTime[out]
time_t GetFileModifyTime( LPCTSTR strFilePath )
{
SYSTEMTIME sysTime;
HANDLE hFile = INVALID_HANDLE_VALUE;
FILETIME localFileTime;
WIN32_FIND_DATA wfd;
memset( &wfd, 0, sizeof(wfd) );
hFile = FindFirstFile( strFilePath, &wfd );
if ( hFile == INVALID_HANDLE_VALUE )
{
return 0;
}
BOOL bRet = FileTimeToLocalFileTime( &wfd.ftLastWriteTime, &localFileTime );
if ( !bRet )
{
return 0;
}
memset( &sysTime, 0, sizeof(sysTime) );
bRet = FileTimeToSystemTime( &localFileTime, &sysTime );
if ( !bRet )
{
return 0;
}
FindClose( hFile );
// 老版本对tm结构体变量的初始化是不科学的,根据之前对本地时间和UTC时间及时区的研究,
struct tm tmFileTime;
memset( &tmFileTime, 0xFF, sizeof(tmFileTime) );
tmFileTime.tm_year = sysTime.wYear - 1900;
tmFileTime.tm_mon = sysTime.wMonth - 1;
tmFileTime.tm_mday = sysTime.wDay;
tmFileTime.tm_hour = sysTime.wHour;
tmFileTime.tm_min = sysTime.wMinute;
tmFileTime.tm_sec = sysTime.wSecond;
return mktime( &tmFileTime );
}
假定我们要清理7天前的日志文件,时间差值定为:
#define LOG_DELETE_INTERVAL_SECOND (7*24*60*60) // 删除7天之前的日志文件
我们去遍历文件夹中的所有日志文件(我们假定该文件夹中存放的都是日志文件,没有其他类型的文件),获取每个日志文件的最后修改时间,然后和当前的时间(调用time函数获取)做差值,如果大于差值宏LOG_DELETE_INTERVAL_SECOND的值,就将之删除掉。相关代码如下:
void DeleteLogFile( LPCTSTR strLogPath )
{
if ( !PathFileExists( strLogPath ) )
{
return;
}
time_t tCurTime = time( NULL ); // 获取当前时间
tstring strFindFileName = strLogPath;
strFindFileName += _T("\\*.*");
WIN32_FIND_DATA wfd;
HANDLE hFindFile = FindFirstFile( strFindFileName.c_str(), &wfd );
if ( hFindFile == INVALID_HANDLE_VALUE )
{
return;
}
while ( true )
{
if ( wfd.cFileName[0] != _T('.') )
{// 非本级或上级目录
if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) // 目录
{
if ( !FindNextFile( hFindFile, &wfd ) )
{
break;
}
continue;
}
else if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM ) // 系统文件,不处理
{
if ( !FindNextFile( hFindFile, &wfd ) )
{
break;
}
continue;
}
else // 用户日志文件
{
tstring strLogFile = strLogPath;
strLogFile += _T("\\");
strLogFile += wfd.cFileName;
time_t tModifyTime = GetFileModifyTime( strLogFile.c_str() );
// 如果是指定时间之前的文件,则将之删除掉(拿当前时间和文件的最后修改时间作比较)
if ( tCurTime - tModifyTime > DELETE_INTERVAL_SECOND )
{
DeleteFile( strLogFile.c_str() );
}
}
}
if ( !FindNextFile( hFindFile, &wfd ) )
{
break;
}
};
FindClose( hFindFile );
}
如果文件夹中包含有子文件夹,则需要修改上面的代码,改成递归遍历文件夹的逻辑即可。