简单实现日志保存, 支持设置日志文件数量, 单个日志文件大小上限, 自动超时保存日志, 日志缓存超限保存
CLogUtils.h
#pragma once
#include
#include
#include
#include
CLogUtils.cpp
#include "CLogUtils.h"
#include
#include
namespace CLogUtils
{
#define FORMAT_BUFFER_CH_SIZE (1024 * 4) //字符串格式化字符缓冲大小(字符)
//全局实例构造
CLogHelper global_logger(
_T(""),
CLogHelper::GetCurrentModuleName(true) + _T("_global"),
LOG_FILE_MAX_SIZE,
LOG_BUF_MAX_COUNT,
AUTO_SAVE_TIME_OUT
);
CLogHelper::CLogHelper(
const _tstring& strDir/* = _T("")*/,
const _tstring& strName/* = _T("")*/,
DWORD nFileSize/* = 1024 * 1024*/,
DWORD nTmpCount/* = 100*/,
DWORD nIntervalTime/* = 60*/
) :
m_strSaveDir(strDir),
m_strSaveName(strName),
m_MaxFileSize(nFileSize),
m_MaxTempCount(nTmpCount),
m_AutoSaveTime(nIntervalTime)
{
if (m_MaxFileSize < LOG_FILE_MAX_SIZE)
{
m_MaxFileSize = LOG_FILE_MAX_SIZE;
}
if (m_AutoSaveTime < AUTO_SAVE_TIME_OUT)
{
m_AutoSaveTime = AUTO_SAVE_TIME_OUT;
}
//默认目录为当前进程目录
if (m_strSaveDir.empty())
{
m_strSaveDir = GetCurrentModuleDir();
}
//默认文件名为当前进程名
if (m_strSaveName.empty())
{
m_strSaveName = GetCurrentModuleName(true);
}
//目录不存在就创建目录
if (!IsDirectory(m_strSaveDir))
{
CreateDir(m_strSaveDir);
}
this->Initialize();
}
CLogHelper::~CLogHelper()
{
this->Uninitialize();
}
bool CLogHelper::Initialize()
{
if (nullptr == m_lpBuf)
{
m_lpBuf = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, FORMAT_BUFFER_CH_SIZE * sizeof(TCHAR));
}
if (nullptr == m_hEvent)
{
m_hEvent = ::CreateEvent(nullptr, false, false, nullptr);
}
//开启一个线程进行自动保存
m_threadAutoSave = std::move(
std::thread([this]()
{
//超时或者退出时转储日志到文件
while (!m_bStop)
{
DWORD dwWait = ::WaitForSingleObject(m_hEvent, m_AutoSaveTime);
switch (dwWait)
{
case WAIT_TIMEOUT:
case WAIT_OBJECT_0:
{
std::lock_guard lock(m_Lock);
this->OutputToFile();
m_logList.clear();
}
break;
default:
break;
}
}
}
)
);
return nullptr != m_lpBuf;
}
void CLogHelper::Uninitialize()
{
if (nullptr != m_lpBuf)
{
::HeapFree(::GetProcessHeap(), 0, m_lpBuf);
m_lpBuf = nullptr;
}
if (nullptr != m_hEvent)
{
m_bStop = true;
::SetEvent(m_hEvent);
}
if (m_threadAutoSave.joinable())
{
m_threadAutoSave.join();
}
if (INVALID_HANDLE_VALUE != m_hFile)
{
::CloseHandle(m_hFile);
m_hFile = INVALID_HANDLE_VALUE;
}
}
void CLogHelper::Logging(
LPCTSTR pstrLevel,
LPCTSTR pstrFile,
LPCTSTR pstrFunc,
UINT nLine,
LPCTSTR pstrFormat,
...
)
{
if (nullptr == m_lpBuf)
{
return;
}
DWORD dwTid = ::GetCurrentThreadId();
_tstring strLogContent = Format(_T("[%s] [%s] [%0.8X(%d)] [%s : %d] [%s] "),
GetCurrentTimeString().c_str(),
pstrLevel,
dwTid,
dwTid,
pstrFile,
nLine,
pstrFunc
);
//格式化日志内容
if (nullptr != m_lpBuf)
{
int nSize = 0;
va_list args;
va_start(args, pstrFormat);
nSize = _vsntprintf_s(m_lpBuf, FORMAT_BUFFER_CH_SIZE, _TRUNCATE, pstrFormat, args);
va_end(args);
}
//获取单行日志内容 + 固定前缀内容 + 真实内容
strLogContent += m_lpBuf;
strLogContent += _T("\r\n");
m_NextItemSize = (DWORD)(strLogContent.size() * sizeof(TCHAR));
std::lock_guard lock(m_Lock);
//首次启动时, 重置大小统计
if (!m_bFirst)
{
InitLogFile();
AdjustLogFile();
m_LogTotalSize = (DWORD)GetFileSize(m_strFilePath);
m_bFirst = true;
}
//单个日志文件大小即将达到或超过阈值则输出到文件, 启用新的文件存储
if ((m_LogTotalSize + m_NextItemSize) >= m_MaxFileSize)
{
OutputToFile();
m_logList.clear();
::CloseHandle(m_hFile);
m_hFile = INVALID_HANDLE_VALUE;
(void)GenerateLogFilePath();
m_LogTotalSize = (DWORD)GetFileSize(m_strFilePath);
AdjustLogFile();
}
//已缓存条目达到阈值则输出到文件
else if (m_logList.size() >= m_MaxTempCount)
{
OutputToFile();
m_logList.clear();
}
//累加统计单个日志文件大小
m_LogTotalSize += m_NextItemSize;
//缓存日志
m_logList.emplace_back(strLogContent);
return;
}
void CLogHelper::Clear()
{
std::lock_guard lock(m_Lock);
if (INVALID_HANDLE_VALUE != m_hFile)
{
::CloseHandle(m_hFile);
m_hFile = INVALID_HANDLE_VALUE;
}
m_logFileList = GetLogFileList(m_strSaveDir);
for (const auto& item: m_logFileList)
{
DeleteArchive(item.second);
}
m_logFileList.clear();
}
void CLogHelper::AdjustLogFile()
{
//检查文件数量是否到达阈值, 到达的话删除前面的文件
if (m_logFileList.size() > HISTORY_FILE_MAX_COUNT)
{
size_t nDeleteCount = m_logFileList.size() - HISTORY_FILE_MAX_COUNT;
size_t nIndex = 0;
//删除多出的文件
for (const auto& item : m_logFileList)
{
DeleteArchive(item.second);
nIndex++;
if (nIndex >= nDeleteCount)
{
break;
}
}
//文件名整体向前移动
_tstring lastPath;
for (const auto& item : m_logFileList)
{
if (lastPath.empty())
{
::MoveFileEx(item.second.c_str(), lastPath.c_str(), MOVEFILE_DELAY_UNTIL_REBOOT);
}
lastPath = item.second;
}
//从日志文件记录列表中删除
for (size_t i = 0; i < nDeleteCount; i++)
{
m_logFileList.erase(m_logFileList.begin());
}
}
}
void CLogHelper::InitLogFile()
{
//如果上次最后一个日志文件大小还能存储日志, 就沿用上次的日志文件
m_logFileList = GetLogFileList(m_strSaveDir);
if (!m_logFileList.empty())
{
auto itLast = m_logFileList.end();
itLast--;
m_nFileTimetamp = itLast->first;
m_strFilePath = itLast->second;
//上次最后一个日志文件不能存储更多日志, 则生成新的日志文件路径
unsigned long long ullFileSize = (DWORD)GetFileSize(m_strFilePath);
if ((ullFileSize + m_NextItemSize) >= m_MaxFileSize)
{
(void)GenerateLogFilePath();
}
}
else
{
(void)GenerateLogFilePath();
}
}
void CLogHelper::GenerateLogFilePath()
{
//得到日志文件时间戳
m_nFileTimetamp = GetCurrentTimestamp();
//得到日志文件路径
m_strFilePath = Format(_T("%s\\%s_%s.log"),
m_strSaveDir.c_str(),
m_strSaveName.c_str(),
TimestampToString(
_T("%04d-%02d-%02d_%02d-%02d-%02d-%03d"),
m_nFileTimetamp).c_str()
);
//创建一下文件(防止在资源管理器中看不到新的日志文件)
m_hFile = CreateFile(m_strFilePath.c_str(),
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
//在文件末尾追加内容
LARGE_INTEGER liDistanceToMove = { 0 };
::SetFilePointerEx(m_hFile, liDistanceToMove, NULL, FILE_END);
m_logFileList.insert(std::make_pair(m_nFileTimetamp, m_strFilePath));
}
std::map CLogHelper::GetLogFileList(const _tstring& strDir)
{
std::map fileList;
WIN32_FIND_DATA findData = { 0 };
HANDLE hFindHandle = INVALID_HANDLE_VALUE;
hFindHandle = FindFirstFile((strDir + _T("\\*.*")).c_str(), &findData);
if (INVALID_HANDLE_VALUE == hFindHandle)
{
return fileList;
}
do
{
_tstring strName = findData.cFileName;
//非目录
if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
//检查输入规则
int nConverted = 0;
SYSTEMTIME st = { 0 };
_tstring strPath = strDir + _T("\\") + strName;
_tstring strPrefix = Format(_T("%s_%%4hd-%%2hd-%%2hd_%%2hd-%%2hd-%%2hd-%%3hd.log"), m_strSaveName.c_str());
nConverted = _stscanf_s(findData.cFileName, strPrefix.c_str(),
&st.wYear, &st.wMonth, &st.wDay, &st.wHour,
&st.wMinute, &st.wSecond, &st.wMilliseconds);
//检查文件名规则是否符合要求
if (7 == nConverted)
{
FILETIME ftFile = { 0 };
FILETIME ftLocal = { 0 };
int64_t timestamp = 0;
::SystemTimeToFileTime(&st, &ftLocal);
::LocalFileTimeToFileTime(&ftLocal, &ftFile);
timestamp = ((int64_t)ftFile.dwHighDateTime << 32) | ftFile.dwLowDateTime;
timestamp = (timestamp - 116444736000000000) / 10000;
fileList.insert(std::make_pair(timestamp, strPath));
}
}
//上一级目录与当前目录跳过
if (0 == _tcscmp(findData.cFileName, _T(".")) || 0 == _tcscmp(findData.cFileName, _T("..")))
{
continue;
}
} while (::FindNextFile(hFindHandle, &findData));
::FindClose(hFindHandle);
return fileList;
}
bool CLogHelper::OutputToFile()
{
bool bSuccess = false;
//没有需要写入的日志
if (m_logList.empty())
{
return true;
}
if (INVALID_HANDLE_VALUE == m_hFile)
{
return false;
}
//写入日志
DWORD dwNumberOfBytesWrite = 0;
for (const auto& item : m_logList)
{
bSuccess = ::WriteFile(m_hFile, item.c_str(), (DWORD)(item.size() * sizeof(TCHAR)), &dwNumberOfBytesWrite, NULL);
if (!bSuccess)
{
break;
}
}
return bSuccess;
}
_tstring CLogHelper::Format(LPCTSTR pstrFormat, ...)
{
_tstring strResult;
if (nullptr != m_lpBuf)
{
int nSize = 0;
va_list args;
va_start(args, pstrFormat);
nSize = _vsntprintf_s(m_lpBuf, FORMAT_BUFFER_CH_SIZE, _TRUNCATE, pstrFormat, args);
va_end(args);
strResult = m_lpBuf;
}
return strResult;
}
_tstring CLogHelper::GetCurrentTimeString()
{
TCHAR szBuf[MAX_PATH] = { 0 };
SYSTEMTIME st = { 0 };
(void)::GetLocalTime(&st);
::StringCchPrintf(szBuf, _countof(szBuf),
_T("%04d-%02d-%02d %02d:%02d:%02d.%d"),
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
);
return szBuf;
}
_tstring CLogHelper::GetCurrentDateString()
{
TCHAR szBuf[MAX_PATH] = { 0 };
SYSTEMTIME st = { 0 };
(void)::GetLocalTime(&st);
::StringCchPrintf(szBuf, _countof(szBuf), _T("%04d-%02d-%02d"), st.wYear, st.wMonth, st.wDay);
return szBuf;
}
int64_t CLogHelper::GetCurrentTimestamp()
{
int64_t timeStamp = 0;
(void)::GetSystemTimeAsFileTime((FILETIME*)&timeStamp);
return (timeStamp - 116444736000000000) / 10000;
}
_tstring CLogHelper::TimestampToString(const _tstring& strFormat, int64_t timestamp)
{
TCHAR szBuf[MAX_PATH] = { 0 };
SYSTEMTIME st = { 0 };
FILETIME ftFile = { 0 };
FILETIME ftLocal = { 0 };
timestamp = timestamp * 10000 + 116444736000000000;
ftFile.dwLowDateTime = timestamp & 0xFFFFFFFF;
ftFile.dwHighDateTime = timestamp >> 32;
::FileTimeToLocalFileTime(&ftFile, &ftLocal);
::FileTimeToSystemTime(&ftLocal, &st);
::StringCchPrintf(szBuf, _countof(szBuf),
strFormat.c_str(),
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
);
return szBuf;
}
_tstring CLogHelper::GetCurrentModulePath()
{
TCHAR szCurPath[MAX_PATH] = { 0 };
::GetModuleFileName(NULL, szCurPath, _countof(szCurPath));
_tstring strResult = szCurPath;
return strResult;
}
_tstring CLogHelper::GetCurrentModuleDir()
{
return GetFileDir(GetCurrentModulePath());
}
_tstring CLogHelper::GetCurrentModuleName(bool bHasExt/* = true*/)
{
return GetFileName(GetCurrentModulePath(), bHasExt);
}
_tstring CLogHelper::GetFileDir(const _tstring& strPath)
{
_tstring strResult;
size_t nIndex = strPath.find_last_of(_T('\\'));
if (nIndex != _tstring::npos)
{
strResult = strPath.substr(0, nIndex);
}
return strResult;
}
_tstring CLogHelper::GetFileName(const _tstring& strPath, bool bHasExt/* = true*/)
{
_tstring strResult = strPath;
size_t nIndex = strResult.find_last_of(_T('\\'));
if (nIndex != _tstring::npos)
{
strResult = strResult.substr(nIndex + 1);
}
if (!bHasExt)
{
nIndex = strResult.find_last_of(_T('.'));
if (nIndex != _tstring::npos)
{
return strResult.substr(0, nIndex);
}
}
return strResult;
}
bool CLogHelper::IsArchive(const _tstring& strPath)
{
WIN32_FILE_ATTRIBUTE_DATA attr = { 0 };
if (!::GetFileAttributesEx(strPath.c_str(), GetFileExInfoStandard, &attr))
{
return false;
}
return attr.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE;
}
bool CLogHelper::IsDirectory(const _tstring& strPath)
{
WIN32_FILE_ATTRIBUTE_DATA attr = { 0 };
if (!::GetFileAttributesEx(strPath.c_str(), GetFileExInfoStandard, &attr))
{
return false;
}
return attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
}
bool CLogHelper::CreateDir(const _tstring& strPath)
{
_tstring strDriver; //驱动器号, 如 D:
_tstring strSubPath = strPath; //路径, 如 Test\1\2\3
if (strPath.empty())
{
return false;
}
//获取盘符
do
{
size_t nFindIndex = strPath.find_first_of(':'); //检查是否有驱动器号
if (nFindIndex == _tstring::npos)
{
break;
}
strDriver = strPath.substr(0, nFindIndex + 1); //得到驱动器号, 如 D:
nFindIndex = strPath.find(_T("\\"), nFindIndex);
if (nFindIndex == _tstring::npos)
{
break;
}
strSubPath = strPath.substr(nFindIndex + 1); //得到路径, 如 Test\1\2\3
} while (false);
_tstring strDestDir;
size_t nFindBegin = 0;
size_t nFindIndex = 0;
do
{
nFindIndex = strSubPath.find(_T("\\"), nFindBegin);
if (nFindIndex != _tstring::npos)
{
strDestDir = strSubPath.substr(0, nFindIndex);
nFindBegin = nFindIndex + 1;
}
else
{
strDestDir = strSubPath;
}
if (!strDriver.empty())
{
strDestDir = strDriver + _T("\\") + strDestDir;
}
if (!::CreateDirectory(strDestDir.c_str(), NULL) && ERROR_ALREADY_EXISTS != ::GetLastError())
{
return false;
}
} while (nFindIndex != _tstring::npos);
return true;
}
bool CLogHelper::DeleteArchive(const _tstring& strPath)
{
if (strPath.empty())
{
return false;
}
return ::DeleteFile(strPath.c_str());
}
unsigned long long CLogHelper::GetFileSize(const _tstring& strPath)
{
unsigned long long ullSize = 0;
WIN32_FILE_ATTRIBUTE_DATA attr = { 0 };
if (!::GetFileAttributesEx(strPath.c_str(), GetFileExInfoStandard, &attr))
{
return 0;
}
ullSize = (unsigned long long)attr.nFileSizeHigh << 32 | attr.nFileSizeLow;
return ullSize;
}
}
使用
main.cpp
#include
#include "CLogUtils.h"
#include
int _tmain(int argc, LPCTSTR argv[])
{
while (true)
{
uint64_t uBegin = CLogUtils::CLogHelper::GetCurrentTimestamp();
uint64_t uEnd = 0;
int nCount = 10000;
std::cout << " CurrentTimestamp: " << uBegin << std::endl;
for (int i = 0; i < nCount; i++)
{
CLogUtils::LOG_INFO(_T("%d %s"), 1024, _T("FlameCyclone"));
}
uEnd = CLogUtils::CLogHelper::GetCurrentTimestamp();
std::cout << "Repeat " << nCount << " Cost time: " << uEnd - uBegin << std::endl;
system("pause");
}
return 0;
}
x64 Debug效果: