Win32 简单日志实现
简单实现日志保存, 支持设置日志文件数量, 单个日志文件大小上限, 自动超时保存日志, 日志缓存超限保存
CLogUtils.h
#pragma once#include <string>
#include <windows.h>
#include <vector>
#include <map>
#include <mutex>
#include <tchar.h>
#include <thread>#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endifnamespace CLogUtils
{#define USR_STRING_BUFFER_CONTENT (1) //使用字符串作为日志缓冲
#define HISTORY_FILE_MAX_COUNT (16) //最多日志文件历史数量
#define AUTO_SAVE_TIME_OUT (1000 * 60) //自动保存超时时间(毫秒)
#define LOG_FILE_MAX_SIZE (1024 * 1024 * 64) //单个日志文件大小阈值(字节)
#define LOG_BUF_MAX_COUNT (512) //日志缓冲大小阈值#define LOG_INFO(format, ...)\global_logger.Logging(_T(" INFO"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);#define LOG_DEBUG(format, ...)\global_logger.Logging(_T("DEBUG"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);#define LOG_WARN(format, ...)\global_logger.Logging(_T(" WARN"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);#define LOG_ERROR(format, ...)\global_logger.Logging(_T("ERROR"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);class CLogHelper{public:#define LogInfo(format, ...)\Logging(_T(" INFO"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);#define LogDebug(format, ...)\Logging(_T("DEBUG"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);#define LogWarn(format, ...)\Logging(_T(" WARN"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);#define LogError(format, ...)\Logging(_T("ERROR"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);public://// @brief: 构造// @param: nFileSize 文件大小阈值(字节)// @param: nTmpCount 缓存条目阈值// @param: nIntervalTime 自动存储时间间隔(毫秒)// @ret: voidCLogHelper(const _tstring& strDir = _T(""),const _tstring& strName = _T(""),DWORD nFileSize = LOG_FILE_MAX_SIZE,DWORD nTmpCount = LOG_BUF_MAX_COUNT,DWORD nIntervalTime = AUTO_SAVE_TIME_OUT);//删除拷贝构造与赋值重载CLogHelper(const CLogHelper&) = delete;CLogHelper& operator = (const CLogHelper&) = delete;~CLogHelper();//// @brief: 记录一条日志// @param: pstrLevel 日志等级// @param: pstrFile 源码文件// @param: pstrFunc 源码函数// @param: nLine 行数// @param: pstrFormat 格式化字符串// @param: ... 可变参数// @ret: voidvoid Logging(LPCTSTR pstrLevel,LPCTSTR pstrFile,LPCTSTR pstrFunc,UINT nLine,LPCTSTR pstrFormat,...);//// @brief: 清空已经存储的日志文件// @ret: voidvoid Clear();//// @brief: 格式化字符串// @param: void// @ret: bool 执行结果_tstring Format(LPCTSTR pstrFormat, ...);//// @brief: 获取目录下文件路径// @ret: std::vector<_tstring> 日志文件列表std::map<int64_t, _tstring> GetLogFileList(const _tstring& strDir);// // @brief: 获取当前进程完全路径// @ret: 当前进程完全路径 如 D:\Software\HxDPortableSetup.exestatic _tstring GetCurrentModulePath();// // @brief: 获取当前进程所在目录// @ret: 当前进程所在目录 如 D:\Softwarestatic _tstring GetCurrentModuleDir();// // @brief: 获取当前进程名// @ret: 当前进程名 如 HxDPortableSetup.exestatic _tstring GetCurrentModuleName(bool bHasExt = false);// // @brief: 获取文件所在文件夹// @param: strPath 文件名, 如: D:\Software\HxDPortableSetup.exe// @ret: 文件夹 如 D:\Softwarestatic _tstring GetFileDir(const _tstring& strPath);// // @brief: 获取文件名// @param: strPath 文件名, 如: D:\Software\HxDPortableSetup.exe// @param: bHasExt 是否包含扩展名// @ret: 文件夹 如 HxDPortableSetupstatic _tstring GetFileName(const _tstring& strPath, bool bHasExt = false);// // @brief: 检查文件是否存在// @param: strPath 文件名, 如: D:\Software\HxDPortableSetup.exe// @ret: 是否存在 存在返回 truestatic bool IsArchive(const _tstring& strPath);// // @brief: 检查文件是否存在// @param: strPath 文件名, 如: D:\Software\HxDPortableSetup.exe// @ret: 是否存在 存在返回 truestatic bool IsDirectory(const _tstring& strPath);//// @brief: 创建目录(递归)// @param: strPath 路径// @ret: 成功返回truestatic bool CreateDir(const _tstring& strPath);//// @brief: 删除文件// @param: strPath 路径// @ret: 成功返回truestatic bool DeleteArchive(const _tstring& strPath);//// @brief: 获取当前时间戳字符串// @param: void// @ret: _tstring 时间戳字符串 如: 2023-10-11 17:43:00.617static _tstring GetCurrentTimeString();//// @brief: 获取当前日期字符串// @param: void// @ret: _tstring 时间戳字符串 如: 2023-10-11static _tstring GetCurrentDateString();//// @brief: 获取当前时间戳// @param: void// @ret: 时间戳(单位: 毫秒) 如: 1697017380617static int64_t GetCurrentTimestamp();//// @brief: 时间戳转字符串 // @param: strFormat 格式化字符串 如: "%04d-%02d-%02d %02d:%02d:%02d.%d"// @param: timestamp 时间戳 如: 1697017380617// @ret: 时间字符串 如: 2023-10-11 17:43:00.617static _tstring TimestampToString(const _tstring& strFormat = _T("%04d-%02d-%02d %02d:%02d:%02d.%d"), int64_t timestamp = 0);//// @brief: 获取文件大小// @param: strPath 路径// @ret: 文件大小unsigned long long GetFileSize(const _tstring& strPath);private://// @brief: 调整日志文件数量// @param: void// @ret: voidvoid AdjustLogFile();//// @brief: 初始化// @param: void// @ret: bool 执行结果bool Initialize();//// @brief: 取消初始化// @param: void// @ret: voidvoid Uninitialize();//// @brief: 初始化日志文件// @param: void// @ret: int 日志文件索引void InitLogFile();//// @brief: 生成日志转储文件路径// @param: void// @ret: voidvoid GenerateLogFilePath();//// @brief: 输出日志到文件// @ret: bool 执行结果bool OutputToFile();private:LPTSTR m_lpBuf = nullptr; //格式化字符串使用的缓冲指针std::mutex m_Lock; //线程安全锁std::vector<_tstring> m_logList; //日志记录std::map<int64_t, _tstring> m_logFileList; //日志文件记录, 按照时间戳排序std::thread m_threadAutoSave; //自动保存线程对象HANDLE m_hEvent = nullptr; //通知事件, 使用自动转储的超时等待HANDLE m_hFile = INVALID_HANDLE_VALUE; //文件句柄, 日志文件写入使用int64_t m_nFileTimetamp = 0; //日志文件时间戳_tstring m_strSaveDir; //日志存放目录_tstring m_strSaveName; //日志文件名_tstring m_strFilePath; //当前日志文件路径bool m_bStop = false; //停止标记bool m_bFirst = false; //首次记录日志标记_tstring m_strLogContent; //日志内容DWORD m_MaxFileSize = 0; //文件大小阈值(到达阈值则转储到文件)DWORD m_MaxTempCount = 0; //缓存条目阈值(到达阈值则转储到文件)DWORD m_AutoSaveTime = 0; //自动保存间隔时间阈值(到达阈值则转储到文件)DWORD m_LogTotalSize = 0; //日志文件统计DWORD m_NextItemSize = 0; //下一条目日志大小DWORD m_LogItemCount = 0; //日志缓冲统计};extern CLogHelper global_logger; //全局静态实例
}
CLogUtils.cpp
#include "CLogUtils.h"
#include <strsafe.h>
#include <tchar.h>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<std::mutex> 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;}}bool CLogHelper::OutputToFile(){DWORD dwNumberOfBytesWrite = 0;bool bSuccess = false;if (INVALID_HANDLE_VALUE == m_hFile){return false;}#if USR_STRING_BUFFER_CONTENT//没有需要写入的日志if (m_strLogContent.empty()){return true;}bSuccess = ::WriteFile(m_hFile, m_strLogContent.c_str(), (DWORD)(m_strLogContent.size() * sizeof(TCHAR)), &dwNumberOfBytesWrite, NULL);m_strLogContent.clear();
#else//没有需要写入的日志if (m_logList.empty()){return true;}for (const auto& item : m_logList){bSuccess = ::WriteFile(m_hFile, item.c_str(), (DWORD)(item.size() * sizeof(TCHAR)), &dwNumberOfBytesWrite, NULL);if (!bSuccess){break;}}#endifreturn bSuccess;}void CLogHelper::Logging(LPCTSTR pstrLevel,LPCTSTR pstrFile,LPCTSTR pstrFunc,UINT nLine,LPCTSTR pstrFormat,...){if (nullptr == m_lpBuf){return;}TCHAR szBuf[MAX_PATH] = { 0 };DWORD dwTid = ::GetCurrentThreadId();_tstring strLogContent;//日志格式前缀 [时间] [等级] [十六进制线程ID(十进制线程ID)] [源码位置 : 行数] [函数名]::StringCchPrintf(szBuf, _countof(szBuf), _T("[%s] [%s] [%0.8X(%d)] [%s : %d] [%s] "),GetCurrentTimeString().c_str(),pstrLevel,dwTid,dwTid,pstrFile,nLine,pstrFunc);strLogContent = szBuf;//格式化日志内容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<std::mutex> 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();}//已缓存条目达到阈值则输出到文件
#if USR_STRING_BUFFER_CONTENTelse if (m_LogItemCount >= m_MaxTempCount){OutputToFile();m_strLogContent.clear();m_LogItemCount = 0;}
#elseelse if (m_logList.size() >= m_MaxTempCount){OutputToFile();m_logList.clear();}#endif#if USR_STRING_BUFFER_CONTENTm_strLogContent += strLogContent;m_LogItemCount++;
#elsem_logList.emplace_back(strLogContent);
#endif//累加统计单个日志文件大小m_LogTotalSize += m_NextItemSize;return;}void CLogHelper::Clear(){std::lock_guard<std::mutex> 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{//打开文件以续写日志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);}}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<int64_t, _tstring> CLogHelper::GetLogFileList(const _tstring& strDir){std::map<int64_t, _tstring> 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;}_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\3if (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 <iostream>
#include "CLogUtils.h"
#include <tchar.h>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效果:
x64 Release效果:
相关文章:

Win32 简单日志实现
简单实现日志保存, 支持设置日志文件数量, 单个日志文件大小上限, 自动超时保存日志, 日志缓存超限保存 CLogUtils.h #pragma once#include <string> #include <windows.h> #include <vector> #include <map> #include <mutex> #include <tc…...
保姆级阿里云ESC服务器安装nodejs或Linux安装nodejs
1. 创建node文件夹 默认 /opt 下边 /opt/node 也可建到其他地方,如/usr/local/node 等 创建后切换到文件夹下 cd /opt/node cd /opt/node2. 下载node并解压 使用命令下载node wget https://nodejs.org/dist/v18.12.0/node-v18.12.0-linux-x64.tar.xz wget https…...

《动手学深度学习 Pytorch版》 9.3 深度循环神经网络
将多层循环神经网络堆叠在一起,通过对几个简单层的组合,产生一个灵活的机制。其中的数据可能与不同层的堆叠有关。 9.3.1 函数依赖关系 将深度架构中的函数依赖关系形式化,第 l l l 个隐藏层的隐状态表达式为: H t ( l ) ϕ l …...

2023-10-19 LeetCode每日一题(同积元组)
2023-10-19每日一题 一、题目编号 1726. 同积元组二、题目链接 点击跳转到题目位置 三、题目描述 给你一个由 不同 正整数组成的数组 nums ,请你返回满足 a * b c * d 的元组 (a, b, c, d) 的数量。其中 a、b、c 和 d 都是 nums 中的元素,且 a ! b…...

GEE:绘制土地利用类型面积分布柱状图
作者:CSDN @ _养乐多_ 本文记录了,在 Google Earth Engine (GEE)中进行随机森林分类后绘制不同类型面积分布柱状图的代码片段。 完整代码请看博客《GEE:随机森林分类教程(样本制作、特征添加、训练、精度、参数优化、贡献度、统计面积)》 柱状图效果如下所示, 文章目…...

2021年03月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试
Python编程(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 下列代码的输出结果是?( ) x 0x10print(x)A:2 B:8 Cÿ…...

全网最丑焊锡教程(仅排针焊接心得)
一直以来玩各种开发板,焊接水平太差始终是阻碍我买性价比高的板子的最大原因。淘宝上好多芯片搭载上肥猪流板子是不包排针焊接的。终于下定决心要克服这个困难。不过,只是会焊接排针在高手面前最好不要说自己会焊锡,这应该是两码事。 首先上…...

重测序基因组:Pi核酸多样性计算
如何计算核酸多样性 Pi 本期笔记分享关于核酸多样性pi计算的方法和相关技巧,主要包括原始数据整理、分组文件设置、计算原理、操作流程、可视化绘图等步骤。 基因组Pi核酸多样性(Pi nucleic acid diversity)是一种遗传学研究中用来描述种群内…...

C++学习之多态详解
目录 多态的实现 例题 重载 重写 重定义的区别 抽象类 多态实现原理 多态的实现 C中的多态是指,当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。多态意味着调用成员函数时,会根据调用函数的对象的类型来执…...

项目经理之识别项目干系人
项目干系人管理是项目管理中的重要一环,识别和管理好项目干系人是成功实施项目的关键之一。本文将介绍4321项目干系人识别方法、干系人等级册以及五步判断法等工具,帮助项目经理更好地识别和管理项目干系人。同时,本文还将介绍干系人能量方格…...

文件列表创建工具 Nifty File Lists mac中文版功能特色
Nifty File Lists mac是一款文件列表创建工具,全面的元数据支持,涵盖了从基本文件信息,如文件名、路径、大小、创建和修改日期等等内容。 Nifty File Lists mac功能特色 全面的 元数据支持强大的多线程元数据提取系统涵盖了从基本文件信息&a…...

人人自媒体的时候,Ai绘画还值得踏入吗?
前言 先说结论,如果你不打算涉足自媒体,平时也从不上网发什么内容去展示自己的话,其实AI绘画对你来说意义不大。但如果你对自媒体感兴趣,会涉及发作品,发内容,甚至去设计图片,那么AI绘画值得你…...
最近学习内容(2023-10-21)
最近学习内容 Linux编译链接命令一条有用的删除可执行文件的bash命令gcc 在macos 的编译选项,其中-g会生成一个.dSYM文件夹to long don’t read 工具的使用gnu bintuils 的使用,但是很可惜macos上的是Mach-O,不是ELFaxel多线程下载器和其余的…...

Java设计模式 | 基于订单批量支付场景,对策略模式和简单工厂模式进行简单实现
基于订单批量支付场景,对策略模式和简单工厂模式进行简单实现 文章目录 策略模式介绍实现抽象策略具体策略1.AliPayStrategy2.WeChatPayStrategy 环境 使用简单工厂来获取具体策略对象支付方式枚举策略工厂接口策略工厂实现 测试使用订单实体类对订单进行批量支付结…...
【组件专题介绍】什么是组件?
组件定义 卡耐基梅隆大学: 一个不透明的功能实体,能够被第三方组装,且符合一个构件模型。 计算机百科全书: 是软件系统中具有相对独立功能、接口由契约指定、和语境有明显依赖关系、可独立部署、可组装的软件实体。 软件构件…...

Mybatis拦截器
MyBatis插件介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。 MyBatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用,通过织入拦截器,在不同节点修改一些执行过程中的关键属性&…...

【项目设计】网络对战五子棋(上)
想回家过年… 文章目录 一、项目前置知识1. websocketpp库1.1 http1.0/1.1和websocket协议1.2 websocketpp库接口的前置认识1.3 搭建一个http/websocket服务器 2. jsoncpp库3. mysqlclient库 二、 项目设计1. 项目模块划分2. 实用工具类模块2.1 日志宏封装2.2 mysql_util2.3 j…...
【Overload游戏引擎细节分析】鼠标键盘控制摄像机原理
在上文中分析了摄像机类的实现,在计算投影视图矩阵时需要给摄像机输入其位置及转动四元数。这两个量一般通过鼠标键盘来控制,从而达到控制摄像机的目的。本文分析一下其控制原理。 Overload的摄像机控制实现在类CameraController中,其有三个个…...

VScode运行SVN拉下来的项目
安装依赖包 pnpm install 启动程序 查看package.json文件中的serve,根据这个启动 pnpm dev 在浏览器使用http://localhost:8848/访问...
jmeter集成kafka测试
Kafka的使用 查看kafka的topic ./kafka-topics --bootstrap-server 10.1.9.84:9092 --list 查看topic信息 ./kafka-topics --bootstrap-server 10.1.9.84:9092 --describe --topic topic_example_1 创建topic 创建topic名为test,分区数为8,副本数为…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...

Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...
【安全篇】金刚不坏之身:整合 Spring Security + JWT 实现无状态认证与授权
摘要 本文是《Spring Boot 实战派》系列的第四篇。我们将直面所有 Web 应用都无法回避的核心问题:安全。文章将详细阐述认证(Authentication) 与授权(Authorization的核心概念,对比传统 Session-Cookie 与现代 JWT(JS…...

RushDB开源程序 是现代应用程序和 AI 的即时数据库。建立在 Neo4j 之上
一、软件介绍 文末提供程序和源码下载 RushDB 改变了您处理图形数据的方式 — 不需要 Schema,不需要复杂的查询,只需推送数据即可。 二、Key Features ✨ 主要特点 Instant Setup: Be productive in seconds, not days 即时设置 :在几秒钟…...

RKNN开发环境搭建2-RKNN Model Zoo 环境搭建
目录 1.简介2.环境搭建2.1 启动 docker 环境2.2 安装依赖工具2.3 下载 RKNN Model Zoo2.4 RKNN模型转化2.5编译C++1.简介 RKNN Model Zoo基于 RKNPU SDK 工具链开发, 提供了目前主流算法的部署例程. 例程包含导出RKNN模型, 使用 Python API, CAPI 推理 RKNN 模型的流程. 本…...