简介
个人实现的轻量化日志助手类, 实现了日志常用功能, 目的是使用简便快捷, 直接包含头文件和源文件即可使用
功能
1.可设置日志文件存放路径以及文件名前缀
2.可设置单个日志文件最大容量, 到限后自动转储到下一个日志文件
3.可设置日志自动保存, 按照时间间隔或日志缓存条目数量
4.可设置最多保存的日志文件数量, 超限会自动删除最旧的日志文件
5.可设置管道模式, 在管道模式下可使用另一个进程来处理日志转存
使用示例
调用: CLogUtils::LOG_INFO(_T("%d %s"), 1024, _T("FlameCyclone"));
日志文件输出内容: 2023-10-25 16:44:57.379 INFO [29288:32416] [E:\gitee\c-log-utils\CLogUtils\CLogUtils\main.cpp:20] [wmain] 1024 FlameCyclone
性能测试
配置1:
CPU: AMD Ryzen 7 6800U with Radeon Graphics
内存: 8GB(6400Mhz) * 2
Release x64 默认模式下速度 50万条 / 秒
Release x64 管道模式下速度 5万条 / 秒
配置2:
CPU: Intel(R) Core(TM) i9-14900K
内存: 16GB(6000Mhz) * 2
Release x64 默认模式下 140万条 / 秒
Release x64 管道模式 54万条 / 秒
CLogUtils.h
#pragma once
#include <string>
#include <windows.h>
#include <vector>
#include <map>
#include <mutex>
#include <tchar.h>
#include <thread>
//请在VS项目属性页 -> C/C++ -> 预处理器 中 添加如下宏指定日志根目录, 用于截取源码文件路径相对路径
//LOG_ROOT_DIR=R"($(ProjectDir))"
//效果:
//添加宏之前: 2023-11-22 13:14:39.644 INFO [34688:22188] [D:\Gitee_FlameCyclone\c-log-utils\CLogUtils\CLogUtils\main.cpp:155] [CLogUtilsTest] 1024 FlameCyclone
//添加宏之后: 2023-11-22 12:31:33.45 INFO [20884:7996] [.\main.cpp:133] [CLogUtilsTest] 1024 FlameCyclone
#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif
#pragma warning(disable:4200)
namespace CLogUtils
{
class CNamedPipe;
#define STRING_CONTENT_BUFFER_ENABLE (1) //使用字符串作为日志缓冲
#define LOG_FILE_COUNT (16) //最多日志文件历史数量
#define LOG_TIMEOUT (1000 * 60) //自动保存超时时间(毫秒)
#define LOG_FILE_SIZE (1024 * 1024 * 16) //单个日志文件大小阈值(字节)
#define LOG_BUF_COUNT (10000) //日志缓冲大小阈值
#define LOG_INFO(format, ...)\
GetInstance().Logging(_T(" INFO"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
#ifdef _DEBUG
#define LOG_DEBUG(format, ...)\
GetInstance().Logging(_T("DEBUG"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
#else
#define LOG_DEBUG(format, ...)\
GetInstance().DoNothing();
#endif
#define LOG_WARN(format, ...)\
GetInstance().Logging(_T(" WARN"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
#define LOG_ERROR(format, ...)\
GetInstance().Logging(_T("ERROR"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
class CLogHelper
{
public:
#define Info(format, ...)\
Logging(_T(" INFO"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
#ifdef _DEBUG
#define Debug(format, ...)\
Logging(_T("DEBUG"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
#else
#define Debug(format, ...)\
DoNothing();
#endif
#define Warn(format, ...)\
Logging(_T(" WARN"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
#define Error(format, ...)\
Logging(_T("ERROR"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
public:
//默认构造
CLogHelper();
//删除拷贝构造与赋值重载
CLogHelper(const CLogHelper&) = delete;
//删除赋值重载
CLogHelper& operator = (const CLogHelper&) = delete;
//析构
~CLogHelper();
//
// @brief: 初始化
// @param: bPipeMode 管道模式 true: 管道模式 false: 默认模式
// @param: strPipeName 管道名
// @param: strDir 存放目录
// @param: strPreName 文件名前缀
// @param: nFileSize 单个日志文件大小阈值(字节), 到达限制转储到下一个文件
// @param: nTempCount 日志缓存条目限制(到达限制转储到文件)
// @param: nTimeout 自动存储时间间隔(毫秒), 到达限制转储到文件
// @param: nFileCount 日志文件数量限制, 到达限制删除最旧的日志文件
// @ret: bool 执行结果
bool Initialize(
bool bPipeMode = false,
const _tstring& strPipeName = _T(""),
const _tstring& strDir = _T(""),
const _tstring& strPreName = _T(""),
DWORD nFileSize = LOG_FILE_SIZE,
DWORD nFileCount = LOG_FILE_COUNT,
DWORD nTempCount = LOG_BUF_COUNT,
DWORD nTimeout = LOG_TIMEOUT
);
//
// @brief: 反初始化
// @ret: void
void Uninitialize();
//
// @brief: 记录一条日志
// @param: pstrLevel 日志等级
// @param: pstrFile 源码文件
// @param: pstrFunc 源码函数
// @param: nLine 行数
// @param: pstrFormat 格式化字符串
// @param: ... 可变参数
// @ret: void
void Logging(
LPCTSTR pstrLevel,
LPCTSTR pstrFile,
LPCTSTR pstrFunc,
UINT nLine,
LPCTSTR pstrFormat,
...
);
//
// @brief: 清空已经存储的日志文件
// @ret: void
void Clear();
//
// @brief: 刷新日志缓冲(输出日志到文件)
// @ret: bool 执行结果
bool FlushBuffers();
//
// @brief: 啥也不干
// @ret: void
void DoNothing();
private:
//
// @brief: 获取目录下文件路径
// @ret: std::vector<_tstring> 日志文件列表
std::map<int64_t, _tstring> _GetLogFileList(const _tstring& strDir);
//
// @brief: 调整日志文件数量
// @param: void
// @ret: void
void _AdjustLogFile();
//
// @brief: 通过管道处理日志
// @ret: bool 执行结果
bool _ProcessByPipe();
//
// @brief: 超时保存处理
// @ret: bool 执行结果
bool _ProcessTimeoutSave();
//
// @brief: 管道方式记录日志
// @ret: bool 执行结果
bool _LoggingByPipe(const _tstring& strLogContent);
//
// @brief: 刷新日志缓冲(输出日志到文件)
// @ret: bool 执行结果
bool _FlushLogBuffers();
//
// @brief: 记录日志
// @ret: bool 执行结果
bool _LoggingContent(const _tstring& strLogContent);
//
// @brief: 初始化
// @param: void
// @ret: bool 执行结果
bool _Initialize();
//
// @brief: 取消初始化
// @param: void
// @ret: void
void _Uninitialize();
//
// @brief: 初始化日志文件
// @param: void
// @ret: int 日志文件索引
void _InitLogFile();
//
// @brief: 生成日志转储文件路径
// @param: void
// @ret: void
void _GenerateLogFilePath();
//
// @brief: 获取默认日志管道名
// @param: void
// @ret: _tstring 管道名
_tstring _GetDefaultPipeName() const;
private:
std::vector<_tstring> m_logList; //日志记录缓冲
std::map<int64_t, _tstring> m_logFileList; //日志文件记录, 按照时间戳排序
std::thread m_tAutoSaveTask; //超时自动保存任务线程
std::thread m_tPipeRecvTask; //管道接收任务线程
std::mutex m_Lock; //线程安全锁
CNamedPipe* m_pRecvPipe = nullptr; //日志接收管道
CNamedPipe* m_pSendPipe = nullptr; //日志发送管道
HANDLE m_hEvent = nullptr; //通知事件, 使用自动转储的超时等待
HANDLE m_hFile = INVALID_HANDLE_VALUE; //文件句柄, 日志文件写入使用
int64_t m_nFileTimetamp = 0; //日志文件时间戳
_tstring m_strSaveDir; //日志存放目录
_tstring m_strSaveName; //日志文件名
_tstring m_strFilePath; //当前日志文件路径
_tstring m_strLogContent; //日志内容
_tstring m_strPipeName; //管道名
bool m_bStop = false; //停止标记
bool m_bFirst = false; //首次记录日志标记
bool m_bPipeMode = false; //管道模式
DWORD m_nFileSize = 0; //文件大小限制(到达限制则转储到文件)
DWORD m_nTempCount = 0; //缓存条目限制(到达限制则转储到文件)
DWORD m_nFileCount = 0; //历史文件数量限制(超限则删除旧文件)
DWORD m_nTimeout = 0; //自动保存超时限制(超时则转储到文件)
DWORD m_nCurFileSize = 0; //日志文件统计
DWORD m_nNextItemSize = 0; //下一条日志大小
DWORD m_nLogItemCount = 0; //日志缓冲统计
};
class CNamedPipe
{
public:
CNamedPipe();
~CNamedPipe();
CNamedPipe(const CNamedPipe& r) = delete;
CNamedPipe& operator = (const CNamedPipe& r) = delete;
//
// @brief: 创建命名管道
// @param: lpName 管道名
// @ret: bool true: 创建成功 false: 创建失败
bool Create(LPCTSTR lpName);
//
// @brief: 等待客户端连接命名管道
// @param: nTimeOut 超时等待(毫秒)
// @ret: bool true: 连接成功 false: 连接失败
bool WaitConnect(DWORD nTimeOut = INFINITE);
//
// @brief: 关闭由Create 创建的管道
// @param: void
// @ret: bool true: 关闭 成功 false: 关闭 失败
bool Disconnect();
//
// @brief: 打开已存在的命名管道
// @param: lpName 管道名
// @ret: bool true: 打开成功 false: 打开失败
bool Open(LPCTSTR lpName, DWORD nTimeOut = INFINITE);
//
// @brief: 管道是否有效
// @param: void
// @ret: bool true: 可用 false: 无效
bool IsValid();
//
// @brief: 关闭管道
// @param: void
// @ret: void
void Close(void);
//
// @brief: 从读取管道数据
// @param: lpData 数据存放缓冲
// @param: nSize 缓冲大小(字节)
// @param: lpBytesRead 指向实际读取大小(字节)的指针
// @param: nTimeOut 读取超时(毫秒)
// @ret: bool true: 读取成功 false: 读取失败
bool Read(LPVOID lpData, DWORD nSize, LPDWORD lpBytesRead = nullptr, DWORD nTimeOut = INFINITE);
//
// @brief: 向管道写入数据
// @param: lpData 写入数据指针
// @param: nSize 写入数据大小(字节)
// @param: lpBytesWritten 指向实际写入大小(字节)的指针
// @param: nTimeOut 写入超时(毫秒)
// @ret: bool true: 写入成功 false: 写入失败
bool Write(LPCVOID lpData, DWORD nSize, LPDWORD lpBytesWritten = nullptr, DWORD nTimeOut = INFINITE);
private:
//
// @brief: 初始化对象占用
// @param: void
// @ret: void
bool Initialize();
//
// @brief: 释放对象占用
// @param: void
// @ret: void
void Uninitialize();
private:
HANDLE m_hNamedPipe = INVALID_HANDLE_VALUE;
HANDLE m_hReadEvent = NULL;
HANDLE m_hWriteEvent = NULL;
HANDLE m_hConnectEvent = NULL;
LPVOID m_pBuffer = nullptr;
bool m_bInit = false;
bool m_bConnected = false;
};
//
// @brief: 格式化字符串
// @param: void
// @ret: bool 执行结果
_tstring Format(LPCTSTR pstrFormat, ...);
//
// @brief: 获取当前进程完全路径
// @ret: 当前进程完全路径 如 D:\Software\HxDPortableSetup.exe
_tstring GetCurrentModulePath();
//
// @brief: 获取当前进程所在目录
// @ret: 当前进程所在目录 如 D:\Software
_tstring GetCurrentModuleDir();
//
// @brief: 获取当前进程名
// @ret: 当前进程名 如 HxDPortableSetup.exe
_tstring GetCurrentModuleName(bool bHasExt = false);
//
// @brief: 获取文件所在文件夹
// @param: strPath 文件名, 如: D:\Software\HxDPortableSetup.exe
// @ret: 文件夹 如 D:\Software
_tstring GetFileDir(const _tstring& strPath);
//
// @brief: 获取文件名
// @param: strPath 文件名, 如: D:\Software\HxDPortableSetup.exe
// @param: bHasExt 是否包含扩展名
// @ret: 文件夹 如 HxDPortableSetup
_tstring GetFileName(const _tstring& strPath, bool bHasExt = false);
//
// @brief: 检查文件是否存在
// @param: strPath 文件名, 如: D:\Software\HxDPortableSetup.exe
// @ret: 是否存在 存在返回 true
bool IsDirectory(const _tstring& strPath);
//
// @brief: 创建目录(递归)
// @param: strPath 路径
// @ret: 成功返回true
bool CreateDir(const _tstring& strPath);
//
// @brief: 删除文件
// @param: strPath 路径
// @ret: 成功返回true
bool DeleteArchive(const _tstring& strPath);
//
// @brief: 获取当前时间戳字符串
// @param: void
// @ret: _tstring 时间戳字符串 如: 2023-10-11 17:43:00.617
_tstring GetCurrentTimeString();
//
// @brief: 获取当前时间戳
// @param: void
// @ret: 时间戳(单位: 毫秒) 如: 1697017380617
int64_t GetCurrentTimestamp();
//
// @brief: 时间戳转字符串
// @param: strFormat 格式化字符串 如: "%04d-%02d-%02d %02d:%02d:%02d.03%d"
// @param: timestamp 时间戳 如: 1697017380617
// @ret: 时间字符串 如: 2023-10-11 17:43:00.617
_tstring TimestampToString(
int64_t timestamp = 0,
const _tstring& strFormat = _T("%04d-%02d-%02d %02d:%02d:%02d.03%d")
);
//
// @brief: 获取文件大小
// @param: strPath 路径
// @ret: 文件大小
unsigned long long GetFileSize(const _tstring& strPath);
CLogHelper& GetInstance();
}
CLogUtils.cpp
#include "CLogUtils.h"
#include <strsafe.h>
#include <tchar.h>
#include <shlwapi.h>
#include <future>
#pragma comment(lib, "Shlwapi.lib")
namespace CLogUtils
{
#define DEFAULT_LOG_FILE_COUNT_MIN (4) //默认最少日志文件历史数量
#define DEFAULT_LOG_TIMEOUT_MIN (1000 * 5) //默认自动保存超时时间(毫秒)
#define DEFAULT_LOG_FILE_SIZE_MIN (1024 * 1024 * 1) //默认单个日志文件大小阈值(字节)
#define DEFAULT_LOG_BUF_COUNT_MIN (256) //默认日志缓冲数量阈值
#define DEFAULT_LOG_FILE_COUNT_MAX (256) //默认最多日志文件历史数量
#define DEFAULT_LOG_TIMEOUT_MAX (1000 * 60) //默认自动保存超时时间(毫秒)
#define DEFAULT_LOG_FILE_SIZE_MAX (1024 * 1024 * 256) //默认单个日志文件大小阈值(字节)
#define DEFAULT_LOG_BUF_COUNT_MAX (1024 * 4) //默认日志缓冲数量阈值
#define PIPE_NAME_PREFIX TEXT(R"(\\.\pipe\)") //管道前缀名
#define PIPE_MAX_TIMEOUT (3000) //管道打开超时
#define PIPE_BUF_MAX_SIZE (1024 * 1024) //管道发送缓冲大小(字节)
#define PIPE_MAX_CONNECT (1) //管道最大实例数量
typedef struct _PIPE_DATA
{
DWORD dwSize = 0;
BYTE data[0];
}PIPE_DATA, * PPIPE_DATA;
//全局实例构造
static CLogHelper g_Instance;
CLogHelper::CLogHelper() :
m_bPipeMode(false),
m_strPipeName(_T("")),
m_strSaveDir(_T("")),
m_strSaveName(_T("")),
m_nFileSize(LOG_FILE_SIZE),
m_nTempCount(LOG_BUF_COUNT),
m_nTimeout(LOG_TIMEOUT),
m_nFileCount(LOG_FILE_COUNT)
{
}
CLogHelper::~CLogHelper()
{
this->Uninitialize();
}
void CLogHelper::DoNothing()
{
}
bool CLogHelper::Initialize(
bool bPipeMode/* = false*/,
const _tstring& strPipeName/* = _T("")*/,
const _tstring& strDir/* = _T("")*/,
const _tstring& strPreName/* = _T("")*/,
DWORD nFileSize/* = LOG_FILE_SIZE*/,
DWORD nFileCount/* = LOG_FILE_COUNT*/,
DWORD nTmpCount/* = LOG_BUF_COUNT*/,
DWORD nTimeout/* = LOG_TIMEOUT*/
)
{
m_bPipeMode = bPipeMode;
m_strPipeName = strPipeName;
m_strSaveDir = strDir;
m_strSaveName = strPreName;
m_nFileSize = nFileSize;
m_nTempCount = nTmpCount;
m_nTimeout = nTimeout;
m_nFileCount = nFileCount;
//最小参数限制
if (m_nFileSize < DEFAULT_LOG_FILE_SIZE_MIN)
{
m_nFileSize = DEFAULT_LOG_FILE_SIZE_MIN;
}
if (m_nFileCount < DEFAULT_LOG_FILE_COUNT_MIN)
{
m_nFileCount = DEFAULT_LOG_FILE_COUNT_MIN;
}
if (m_nTempCount < DEFAULT_LOG_BUF_COUNT_MIN)
{
m_nTempCount = DEFAULT_LOG_BUF_COUNT_MIN;
}
if (m_nTimeout < DEFAULT_LOG_TIMEOUT_MIN)
{
m_nTimeout = DEFAULT_LOG_TIMEOUT_MIN;
}
//最大参数限制
if (m_nFileSize > DEFAULT_LOG_FILE_SIZE_MAX)
{
m_nFileSize = DEFAULT_LOG_FILE_SIZE_MAX;
}
if (m_nFileCount > DEFAULT_LOG_FILE_COUNT_MAX)
{
m_nFileCount = DEFAULT_LOG_FILE_COUNT_MAX;
}
if (m_nTempCount > DEFAULT_LOG_BUF_COUNT_MAX)
{
m_nTempCount = DEFAULT_LOG_BUF_COUNT_MAX;
}
if (m_nTimeout > DEFAULT_LOG_TIMEOUT_MAX)
{
m_nTimeout = DEFAULT_LOG_TIMEOUT_MAX;
}
//默认目录为当前进程目录
if (m_strSaveDir.empty())
{
m_strSaveDir = GetCurrentModuleDir();
}
//默认文件名为当前进程名
if (m_strSaveName.empty())
{
m_strSaveName = GetCurrentModuleName(true);
}
//目录不存在就创建目录
if (!IsDirectory(m_strSaveDir))
{
CreateDir(m_strSaveDir);
}
if (m_strPipeName.empty())
{
m_strPipeName = _GetDefaultPipeName();
}
return this->_Initialize();
}
void CLogHelper::Uninitialize()
{
_Uninitialize();
}
bool CLogHelper::_Initialize()
{
_Uninitialize();
// 管道模式
if (m_bPipeMode)
{
m_pRecvPipe = new (std::nothrow) CNamedPipe;
m_pSendPipe = new (std::nothrow) CNamedPipe;
if (nullptr == m_pRecvPipe)
{
return false;
}
if (nullptr == m_pSendPipe)
{
return false;
}
std::promise<bool> m;
std::future<bool> p = m.get_future();
m_tPipeRecvTask = std::move(
std::thread([this, &m]() -> void
{
m.set_value(true);
_ProcessByPipe();
}
)
);
//等待线程启动完毕
p.get();
}
// 超时自动保存任务
{
if (NULL == m_hEvent)
{
m_hEvent = ::CreateEvent(nullptr, false, false, nullptr);
}
if (NULL == m_hEvent)
{
return false;
}
std::promise<bool> m;
std::future<bool> p = m.get_future();
m_tAutoSaveTask = std::move(
std::thread([this, &m]()
{
m.set_value(true);
_ProcessTimeoutSave();
}
)
);
//等待线程启动完毕
p.get();
}
return true;
}
void CLogHelper::_Uninitialize()
{
if (!m_logList.empty() || !m_strLogContent.empty())
{
FlushBuffers();
}
if (INVALID_HANDLE_VALUE != m_hFile)
{
::CloseHandle(m_hFile);
m_hFile = INVALID_HANDLE_VALUE;
}
if (NULL != m_hEvent)
{
m_bStop = true;
::SetEvent(m_hEvent);
}
if (m_tAutoSaveTask.joinable())
{
m_tAutoSaveTask.join();
}
if (NULL != m_hEvent)
{
::CloseHandle(m_hEvent);
m_hEvent = NULL;
}
if (m_bPipeMode)
{
if (m_pRecvPipe)
{
m_pRecvPipe->Close();
}
if (m_pSendPipe)
{
m_pSendPipe->Close();
}
if (m_tPipeRecvTask.joinable())
{
m_tPipeRecvTask.join();
}
}
if (nullptr != m_pRecvPipe)
{
delete m_pRecvPipe;
m_pRecvPipe = nullptr;
}
if (nullptr != m_pSendPipe)
{
delete m_pSendPipe;
m_pSendPipe = nullptr;
}
m_bFirst = false;
m_bStop = false;
}
bool CLogHelper::_LoggingByPipe(const _tstring& strLogContent)
{
bool bSuccess = false;
if (nullptr == m_pSendPipe)
{
return false;
}
if (!m_pSendPipe->IsValid())
{
if (!m_pSendPipe->Open(m_strPipeName.c_str(), 1000))
{
return false;
}
}
//写入日志内容到管道, 交给另一端处理(可用跨进程)
bSuccess = m_pSendPipe->Write(strLogContent.c_str(), (DWORD)((strLogContent.size() + 1) * sizeof(TCHAR)));
if (!bSuccess)
{
m_pSendPipe->Close();
}
return bSuccess;
}
bool CLogHelper::_FlushLogBuffers()
{
DWORD dwNumberOfBytesWrite = 0;
bool bSuccess = false;
if (INVALID_HANDLE_VALUE == m_hFile)
{
return false;
}
#if STRING_CONTENT_BUFFER_ENABLE
//没有需要写入的日志
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;
}
}
#endif
return bSuccess;
}
bool CLogHelper::_LoggingContent(const _tstring& strLogContent)
{
//获取单行日志内容 + 固定前缀内容 + 真实内容
m_nNextItemSize = (DWORD)(strLogContent.size() * sizeof(TCHAR));
std::lock_guard<std::mutex> lock(m_Lock);
//首次启动时, 重置大小统计
if (!m_bFirst)
{
_InitLogFile();
_AdjustLogFile();
m_nCurFileSize = (DWORD)GetFileSize(m_strFilePath);
m_bFirst = true;
}
//单个日志文件大小即将达到或超过阈值则输出到文件, 启用新的文件存储
if ((m_nCurFileSize + m_nNextItemSize) >= m_nFileSize)
{
_FlushLogBuffers();
m_logList.clear();
::CloseHandle(m_hFile);
m_hFile = INVALID_HANDLE_VALUE;
(void)_GenerateLogFilePath();
m_nCurFileSize = (DWORD)GetFileSize(m_strFilePath);
_AdjustLogFile();
}
//已缓存条目达到阈值则输出到文件
#if STRING_CONTENT_BUFFER_ENABLE
else if (m_nLogItemCount >= m_nTempCount)
{
_FlushLogBuffers();
m_strLogContent.clear();
m_nLogItemCount = 0;
}
#else
else if (m_logList.size() >= m_nTempCount)
{
_FlushLogBuffers();
m_logList.clear();
}
#endif
#if STRING_CONTENT_BUFFER_ENABLE
m_strLogContent += strLogContent;
m_nLogItemCount++;
#else
m_logList.emplace_back(strLogContent);
#endif
//累加统计单个日志文件大小
m_nCurFileSize += m_nNextItemSize;
return true;
}
bool CLogHelper::_ProcessTimeoutSave()
{
while (!m_bStop)
{
DWORD dwWait = ::WaitForSingleObject(m_hEvent, m_nTimeout);
switch (dwWait)
{
case WAIT_TIMEOUT:
case WAIT_OBJECT_0:
{
std::lock_guard<std::mutex> lock(m_Lock);
this->_FlushLogBuffers();
m_logList.clear();
}
break;
default:
break;
}
}
return true;
}
bool CLogHelper::_ProcessByPipe()
{
if (m_pRecvPipe->IsValid())
{
return true;
}
if (!m_pRecvPipe->Create(m_strPipeName.c_str()))
{
return false;
}
DWORD dwPipeBufSize = PIPE_BUF_MAX_SIZE;
LPTSTR lpData = nullptr;
bool bFailed = false;
lpData = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwPipeBufSize);
if (nullptr == lpData)
{
return false;
}
while (!m_bStop && !bFailed)
{
if (!m_pRecvPipe->WaitConnect())
{
_tprintf(_T("WaitConnect failed!\r\n"));
break;
}
while (!m_bStop)
{
bool isSuccess = m_pRecvPipe->Read(lpData, dwPipeBufSize);
if (isSuccess)
{
_LoggingContent(lpData);
}
if (ERROR_BROKEN_PIPE == ::GetLastError())
{
m_pRecvPipe->Disconnect();
if (!m_pRecvPipe->WaitConnect())
{
_tprintf(_T("WaitConnect failed!\r\n"));
bFailed = true;
break;
}
}
if (!m_pRecvPipe->IsValid())
{
break;
}
}
}
this->_FlushLogBuffers();
m_logList.clear();
if (nullptr != lpData)
{
::HeapFree(::GetProcessHeap(), 0, lpData);
lpData = nullptr;
}
return true;
}
void CLogHelper::Logging(
LPCTSTR pstrLevel,
LPCTSTR pstrFile,
LPCTSTR pstrFunc,
UINT nLine,
LPCTSTR pstrFormat,
...
)
{
if (nullptr == pstrFormat)
{
return;
}
TCHAR szBuf[MAX_PATH] = { 0 };
DWORD dwPid = ::GetCurrentProcessId();
DWORD dwTid = ::GetCurrentThreadId();
_tstring strLogContent;
SYSTEMTIME st = { 0 };
(void)::GetLocalTime(&st);
#ifdef LOG_ROOT_DIR
//日志格式前缀 [时间] [等级] [十进制进程ID:十进制线程ID] [源码位置:行数] [函数名]
//相对路径显示源码文件路径
_TCHAR szRelativePath[MAX_PATH] = { 0 };
if (::PathRelativePathTo(szRelativePath, _T(LOG_ROOT_DIR), FILE_ATTRIBUTE_DIRECTORY, pstrFile, 0))
{
::StringCchPrintf(
szBuf,
_countof(szBuf),
_T("%04d-%02d-%02d %02d:%02d:%02d.%03d %s [%d:%d] [%s:%d] [%s] "),
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
pstrLevel,
dwPid,
dwTid,
szRelativePath,
nLine,
pstrFunc
);
}
else
{
::StringCchPrintf(
szBuf,
_countof(szBuf),
_T("%04d-%02d-%02d %02d:%02d:%02d.%03d %s [%d:%d] [%s:%d] [%s] "),
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
pstrLevel,
dwPid,
dwTid,
pstrFile,
nLine,
pstrFunc
);
}
#else
//日志格式前缀 [时间] [等级] [十进制进程ID:十进制线程ID] [源码位置:行数] [函数名]
::StringCchPrintf(
szBuf,
_countof(szBuf),
_T("%04d-%02d-%02d %02d:%02d:%02d.%03d %s [%d:%d] [%s:%d] [%s] "),
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
pstrLevel,
dwPid,
dwTid,
pstrFile,
nLine,
pstrFunc
);
#endif
strLogContent = szBuf;
va_list args;
va_start(args, pstrFormat);
LPTSTR pFormatBuf = nullptr; //格式化日志缓冲
DWORD dwFormatBufCch = MAX_PATH; //格式化日志缓冲大小
do
{
//分配缓冲
pFormatBuf = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwFormatBufCch * sizeof(TCHAR));
if (nullptr == pFormatBuf)
{
break;
}
//成功则赋值字符串并终止循环
if (-1 != _vsntprintf_s(pFormatBuf, dwFormatBufCch, _TRUNCATE, pstrFormat, args))
{
strLogContent += pFormatBuf;
break;
}
//释放缓冲, 待下次重新分配
::HeapFree(::GetProcessHeap(), 0, pFormatBuf);
pFormatBuf = nullptr;
//超限终止处理
if (dwFormatBufCch >= INT32_MAX)
{
break;
}
dwFormatBufCch *= 2;
} while (true);
va_end(args);
//释放缓冲
if (nullptr != pFormatBuf)
{
::HeapFree(::GetProcessHeap(), 0, pFormatBuf);
pFormatBuf = nullptr;
}
strLogContent += _T("\r\n");
if (m_bPipeMode)
{
_LoggingByPipe(strLogContent);
}
else
{
_LoggingContent(strLogContent);
}
}
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();
}
bool CLogHelper::FlushBuffers()
{
std::lock_guard<std::mutex> lock(m_Lock);
return _FlushLogBuffers();
}
void CLogHelper::_AdjustLogFile()
{
//检查文件数量是否到达阈值, 到达的话删除前面的文件
if (m_logFileList.size() > m_nFileCount)
{
size_t nDeleteCount = m_logFileList.size() - m_nFileCount;
//从日志文件记录列表中删除
for (size_t i = 0; i < nDeleteCount; i++)
{
auto itBegin = m_logFileList.begin();
DeleteArchive(itBegin->second);
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_nNextItemSize) >= m_nFileSize)
{
(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();
}
}
//
// @brief: 获取日志管道名
// @param: void
// @ret: _tstring 管道名
_tstring CLogHelper::_GetDefaultPipeName() const
{
return Format(_T("%s_%s"), m_strSaveDir.c_str(), m_strSaveName.c_str());
}
void CLogHelper::_GenerateLogFilePath()
{
//得到日志文件时间戳
m_nFileTimetamp = GetCurrentTimestamp();
//得到日志文件路径
m_strFilePath = Format(_T("%s\\%s_%s.log"),
m_strSaveDir.c_str(),
m_strSaveName.c_str(),
TimestampToString(m_nFileTimetamp, _T("%04d-%02d-%02d_%02d-%02d-%02d-%03d")).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));
}
CNamedPipe::CNamedPipe() :
m_pBuffer(nullptr),
m_hNamedPipe(INVALID_HANDLE_VALUE),
m_hReadEvent(NULL),
m_hWriteEvent(NULL),
m_bConnected(false),
m_bInit(false)
{
//初始化读写缓冲与事件句柄
Initialize();
}
CNamedPipe::~CNamedPipe()
{
//释放读写缓冲与事件句柄
Uninitialize();
}
bool CNamedPipe::Create(LPCTSTR lpName)
{
TCHAR szPipeName[MAX_PATH];
SECURITY_ATTRIBUTES sa = { 0 };
SECURITY_DESCRIPTOR sd = { 0 };
bool isSuccess = false;
sa.nLength = sizeof(sa);
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = &sd;
if (INVALID_HANDLE_VALUE != m_hNamedPipe)
{
return true;
}
//设置权限, 防止低权限进程不能打开高权限进程创建的管道
(void)::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
(void)::SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
(void)::StringCchPrintf(szPipeName, _countof(szPipeName), TEXT("%s%s"), PIPE_NAME_PREFIX, lpName);
do
{
m_hNamedPipe = ::CreateNamedPipe(
szPipeName,
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_MAX_CONNECT,
PIPE_BUF_MAX_SIZE,
PIPE_BUF_MAX_SIZE,
PIPE_MAX_TIMEOUT,
&sa
);
if (INVALID_HANDLE_VALUE == m_hNamedPipe)
{
break;
}
isSuccess = true;
} while (false);
if (!isSuccess)
{
this->Close();
}
return isSuccess;
}
bool CNamedPipe::Open(LPCTSTR lpName, DWORD nTimeOut/* = INFINITE*/)
{
TCHAR szPipeName[MAX_PATH] = { 0 };
bool isSuccess = false;
(void)::StringCchPrintf(szPipeName, _countof(szPipeName), TEXT("%s%s"), PIPE_NAME_PREFIX, lpName);
if (INVALID_HANDLE_VALUE != m_hNamedPipe)
{
return true;
}
ULONGLONG ullCurTick = ::GetTickCount64();
do
{
m_hNamedPipe = ::CreateFile(
szPipeName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL
);
//管道句柄有效则终止循环
if (INVALID_HANDLE_VALUE != m_hNamedPipe)
{
isSuccess = true;
break;
}
//若错误原因不是因为所有管道范例都在使用中, 则退出循环
if (ERROR_PIPE_BUSY != ::GetLastError())
{
break;
}
//等待命名管道的实例可用于连接
if (::WaitNamedPipe(szPipeName, 1000))
{
continue;
}
//无限等待则不需要检查超时
if (INFINITE == nTimeOut)
{
continue;
}
//执行操作超时则退出循环
if (::GetTickCount64() - ullCurTick > nTimeOut)
{
break;
}
} while (INVALID_HANDLE_VALUE == m_hNamedPipe);
if (!isSuccess)
{
this->Close();
}
return isSuccess;
}
bool CNamedPipe::WaitConnect(DWORD nTimeOut)
{
OVERLAPPED Overlapped = { 0 };
bool isConnected = false;
if (INVALID_HANDLE_VALUE == m_hNamedPipe)
{
return false;
}
Overlapped.hEvent = m_hConnectEvent;
isConnected = ::ConnectNamedPipe(m_hNamedPipe, &Overlapped);
if (!isConnected)
{
DWORD dwError = ::GetLastError();
//管道关闭中
if (ERROR_NO_DATA == dwError)
{
isConnected = false;
}
else if (ERROR_IO_PENDING == dwError)//操作处于挂起状态
{
if (WAIT_OBJECT_0 == ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
{
isConnected = true;
}
}
else if (ERROR_PIPE_CONNECTED == dwError)//管道已经连接
{
isConnected = true;
}
}
m_bConnected = isConnected;
return isConnected;
}
bool CNamedPipe::Disconnect()
{
if (INVALID_HANDLE_VALUE == m_hNamedPipe)
{
return false;
}
//参数句柄必须由 CreateNamedPipe 函数创建
return ::DisconnectNamedPipe(m_hNamedPipe);
}
void CNamedPipe::Close()
{
if (INVALID_HANDLE_VALUE != m_hNamedPipe)
{
if (m_bConnected)
{
::FlushFileBuffers(m_hNamedPipe);
::DisconnectNamedPipe(m_hNamedPipe);
m_bConnected = false;
}
::CloseHandle(m_hNamedPipe);
m_hNamedPipe = INVALID_HANDLE_VALUE;
}
if (m_hReadEvent)
{
::SetEvent(m_hReadEvent);
::CloseHandle(m_hReadEvent);
m_hReadEvent = NULL;
}
if (m_hWriteEvent)
{
::SetEvent(m_hWriteEvent);
::CloseHandle(m_hWriteEvent);
m_hWriteEvent = NULL;
}
if (m_hConnectEvent)
{
::SetEvent(m_hConnectEvent);
::CloseHandle(m_hConnectEvent);
m_hConnectEvent = NULL;
}
}
bool CNamedPipe::IsValid()
{
return INVALID_HANDLE_VALUE != m_hNamedPipe;
}
bool CNamedPipe::Read(LPVOID lpData, DWORD nSize, LPDWORD lpBytesRead/* = nullptr*/, DWORD nTimeOut)
{
OVERLAPPED Overlapped = { 0 };
Overlapped.hEvent = m_hReadEvent;
DWORD dwBytesTransferred = 0;
bool isSuccess = false;
if (nullptr == m_pBuffer ||
nullptr == lpData ||
0 == nSize ||
nSize > PIPE_BUF_MAX_SIZE
)
{
return false;
}
PPIPE_DATA pData = reinterpret_cast<PPIPE_DATA>(m_pBuffer);
if (!::ReadFile(m_hNamedPipe, &pData->dwSize, sizeof(PIPE_DATA), NULL, &Overlapped))
{
//管道已结束
if (ERROR_BROKEN_PIPE == ::GetLastError())
{
return false;
}
if (ERROR_IO_PENDING != ::GetLastError())
{
return false;
}
if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
{
return false;
}
}
if (pData->dwSize > PIPE_BUF_MAX_SIZE)
{
return false;
}
if (!::ReadFile(m_hNamedPipe, pData->data, pData->dwSize, NULL, &Overlapped))
{
if (ERROR_IO_PENDING != ::GetLastError())
{
return false;
}
if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
{
return false;
}
}
if (::GetOverlappedResult(m_hNamedPipe, &Overlapped, &dwBytesTransferred, true))
{
isSuccess = true;
if (lpBytesRead)
{
*lpBytesRead = dwBytesTransferred;
}
}
if (isSuccess)
{
if (nSize < pData->dwSize)
{
::memcpy_s(lpData, nSize, pData->data, nSize);
}
else
{
::memcpy_s(lpData, nSize, pData->data, pData->dwSize);
}
}
return isSuccess;
}
bool CNamedPipe::Write(LPCVOID lpData, DWORD nSize, LPDWORD lpBytesWritten/* = nullptr*/, DWORD nTimeOut)
{
OVERLAPPED Overlapped = { 0 };
Overlapped.hEvent = m_hWriteEvent;
DWORD dwBytesTransferred = 0;
bool isSuccess = false;
if (nullptr == m_pBuffer ||
nullptr == lpData ||
0 == nSize ||
nSize > PIPE_BUF_MAX_SIZE
)
{
return false;
}
PPIPE_DATA pData = reinterpret_cast<PPIPE_DATA>(m_pBuffer);
DWORD dwBytesToWrite = nSize + sizeof(PIPE_DATA);
pData->dwSize = nSize;
::memcpy_s(pData->data, PIPE_BUF_MAX_SIZE, lpData, nSize);
if (::WriteFile(m_hNamedPipe, pData, dwBytesToWrite, NULL, &Overlapped))
{
return true;
}
//管道正在被关闭
if (ERROR_NO_DATA == ::GetLastError())
{
return false;
}
//管道已结束
if (ERROR_BROKEN_PIPE == ::GetLastError())
{
return false;
}
//重叠
if (ERROR_IO_PENDING != ::GetLastError())
{
return false;
}
if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
{
return false;
}
if (::GetOverlappedResult(m_hNamedPipe, &Overlapped, &dwBytesTransferred, true))
{
isSuccess = true;
if (lpBytesWritten)
{
*lpBytesWritten = dwBytesTransferred;
}
}
return isSuccess;
}
bool CNamedPipe::Initialize()
{
bool isSuccess = false;
if (m_bInit)
{
return true;
}
do
{
if (nullptr == m_pBuffer)
{
m_pBuffer = ::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, PIPE_BUF_MAX_SIZE + sizeof(PIPE_DATA));
}
if (NULL == m_hReadEvent)
{
m_hReadEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
}
if (NULL == m_hWriteEvent)
{
m_hWriteEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
}
if (NULL == m_hConnectEvent)
{
m_hConnectEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
}
//任意事件创建失败则终止
if ((NULL == m_hReadEvent) || (NULL == m_hWriteEvent) || (NULL == m_hConnectEvent) )
{
break;
}
isSuccess = true;
} while (false);
if (!isSuccess)
{
Uninitialize();
}
m_bInit = isSuccess;
return m_bInit;
}
void CNamedPipe::Uninitialize()
{
if (!m_bInit)
{
return;
}
//关闭管道
this->Close();
//释放读写缓冲
if (nullptr != m_pBuffer)
{
::HeapFree(::GetProcessHeap(), 0, m_pBuffer);
m_pBuffer = nullptr;
}
m_bInit = false;
}
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 CLogUtils::Format(LPCTSTR pstrFormat, ...)
{
_tstring strResult;
LPTSTR lpBuf = nullptr;
DWORD dwCchCount = MAX_PATH;
if (nullptr == pstrFormat)
{
return strResult;
}
va_list args;
va_start(args, pstrFormat);
do
{
//分配缓冲
lpBuf = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwCchCount * sizeof(TCHAR));
if (nullptr == lpBuf)
{
break;
}
//成功则赋值字符串并终止循环
if (-1 != _vsntprintf_s(lpBuf, dwCchCount, _TRUNCATE, pstrFormat, args))
{
strResult = lpBuf;
break;
}
//释放缓冲, 待下次重新分配
::HeapFree(::GetProcessHeap(), 0, lpBuf);
lpBuf = nullptr;
//缓冲字符数在合理范围内则扩大2倍
if (dwCchCount < INT32_MAX)
{
dwCchCount *= 2;
}
else
{
//超限终止处理
break;
}
} while (true);
va_end(args);
//释放缓冲
if (nullptr != lpBuf)
{
::HeapFree(::GetProcessHeap(), 0, lpBuf);
lpBuf = nullptr;
}
return strResult;
}
_tstring CLogUtils::GetCurrentTimeString()
{
TCHAR szBuf[MAX_PATH] = { 0 };
SYSTEMTIME st = { 0 };
(void)::GetLocalTime(&st);
::StringCchPrintf(
szBuf,
_countof(szBuf),
_T("%04d-%02d-%02d %02d:%02d:%02d.%03d"),
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
);
return szBuf;
}
int64_t CLogUtils::GetCurrentTimestamp()
{
int64_t timeStamp = 0;
(void)::GetSystemTimeAsFileTime((FILETIME*)&timeStamp);
return (timeStamp - 116444736000000000) / 10000;
}
_tstring CLogUtils::TimestampToString(int64_t timestamp, const _tstring& strFormat)
{
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 CLogUtils::GetCurrentModulePath()
{
TCHAR szCurPath[MAX_PATH] = { 0 };
::GetModuleFileName(NULL, szCurPath, _countof(szCurPath));
_tstring strResult = szCurPath;
return strResult;
}
_tstring CLogUtils::GetCurrentModuleDir()
{
return GetFileDir(GetCurrentModulePath());
}
_tstring CLogUtils::GetCurrentModuleName(bool bHasExt/* = true*/)
{
return GetFileName(GetCurrentModulePath(), bHasExt);
}
_tstring CLogUtils::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 CLogUtils::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 CLogUtils::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 CLogUtils::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 CLogUtils::DeleteArchive(const _tstring& strPath)
{
if (strPath.empty())
{
return false;
}
return ::DeleteFile(strPath.c_str());
}
unsigned long long CLogUtils::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;
}
CLogHelper& GetInstance()
{
return g_Instance;
}
}
使用
main.cpp
#include <iostream>
#include <algorithm>
#include <strsafe.h>
#include <stdio.h>
#include "CLogUtils.h"
void PrintHelp();
void PrintHelp_en_us();
void PrintHelp_zh_cn();
void CLogUtilsTest(int nTestCount, _tstring strPipe, _tstring strDir, _tstring strName, int nFileCount, int nFileSize, int nBufCount, int nTimeOut);
int _tmain(int argc, LPCTSTR argv[])
{
::setlocale(LC_ALL, "");
_tstring strLogDir;
_tstring strLogName;
_tstring strPipeName;
DWORD nTestCount = 100000;
DWORD nFileSize = 1024 * 1024 * 4;
DWORD nTempCount = 1000;
DWORD nTimeout = 5000;
DWORD nFileCount = 16;
bool bSrv = true;
if (argc <= 1)
{
PrintHelp();
//return -1;
}
for (int i = 1; i < argc; i++)
{
_tstring strSwitchValue = argv[i];
if (!(_T('-') == strSwitchValue[0] || _T('/') == strSwitchValue[0]))
{
continue;
}
_tstring strHelp = strSwitchValue.substr(1);
if (strHelp == _T("help") || strHelp == _T("?"))
{
PrintHelp();
return 0;
}
size_t nEqual = strSwitchValue.find(_T('='));
if (_tstring::npos == nEqual)
{
continue;
}
_tstring strName = strSwitchValue.substr(1, nEqual - 1);
_tstring strValue = strSwitchValue.substr(nEqual + 1);
std::transform(strName.begin(), strName.end(), strName.begin(), [](TCHAR ch)->TCHAR {
if (ch >= _T('A') && ch <= _T('Z')) ch &= ~0x20;
return ch;
});
if (strName == _T("d"))
{
strLogDir = strValue;
continue;
}
if (strName == _T("n"))
{
strLogName = strValue;
continue;
}
if (strName == _T("a"))
{
_stscanf_s(strValue.c_str(), _T("%d"), &nTestCount);
continue;
}
if (strName == _T("p"))
{
strPipeName = strValue;
continue;
}
if (strName == _T("f"))
{
_stscanf_s(strValue.c_str(), _T("%d"), &nFileCount);
continue;
}
if (strName == _T("s"))
{
_stscanf_s(strValue.c_str(), _T("%d"), &nFileSize);
continue;
}
if (strName == _T("c"))
{
_stscanf_s(strValue.c_str(), _T("%d"), &nTempCount);
continue;
}
if (strName == _T("t"))
{
_stscanf_s(strValue.c_str(), _T("%d"), &nTimeout);
continue;
}
if (strName == _T("srv"))
{
bSrv = _tcstoul(strValue.c_str(), nullptr, 10) != 0;
continue;
}
}
if (bSrv)
{
CLogUtils::GetInstance().Initialize(true, _T("1"), _T(""), _T("Srv"), nFileSize, nFileCount, nTempCount, nTimeout * 1000);
system("pause");
}
else
{
while (true)
{
CLogUtilsTest(nTestCount, strPipeName, strLogDir, strLogName, nFileSize, nFileCount, nTempCount, nTimeout * 1000);
system("pause");
}
}
return 0;
}
void CLogUtilsTest(int nTestCount, _tstring strPipe, _tstring strDir, _tstring strName, int nFileSize, int nFileCount, int nBufCount, int nTimeOut)
{
uint64_t uBegin = 0;
uint64_t uEnd = 0;
uint64_t uCost = 0;
double lfSpeed = 0.0f;
if (!strPipe.empty())
{
CLogUtils::GetInstance().Initialize(true, strPipe, strDir, strName, nFileSize, nFileCount, nBufCount, nTimeOut);
uBegin = CLogUtils::GetCurrentTimestamp();
for (int i = 0; i < nTestCount; i++)
{
CLogUtils::GetInstance().Info(_T("%d %s"), 1024, _T("FlameCyclone"));
}
CLogUtils::GetInstance().FlushBuffers();
uEnd = CLogUtils::GetCurrentTimestamp();
uCost = uEnd - uBegin;
lfSpeed = (double)nTestCount * 1000 / (uCost);
_tprintf(
_T("Repeat %d tims, cost time: %lld, speed: %lf/S\r\n"),
nTestCount,
uCost,
lfSpeed
);
}
else
{
CLogUtils::GetInstance().Initialize(false, strPipe, strDir, strName, nFileSize, nFileCount, nBufCount, nTimeOut);
uBegin = CLogUtils::GetCurrentTimestamp();
for (int i = 0; i < nTestCount; i++)
{
CLogUtils::GetInstance().Info(_T("%d %s"), 1024, _T("FlameCyclone"));
}
CLogUtils::GetInstance().FlushBuffers();
uEnd = CLogUtils::GetCurrentTimestamp();
uCost = uEnd - uBegin;
lfSpeed = (double)nTestCount * 1000 / (uCost);
_tprintf(
_T("Repeat %d tims, cost time: %lld, speed: %lf/S\r\n"),
nTestCount,
uCost,
lfSpeed
);
}
}
void PrintHelp()
{
LANGID langID = ::GetThreadUILanguage();
if (LANG_CHINESE == PRIMARYLANGID(langID))
{
PrintHelp_zh_cn();
}
else
{
PrintHelp_en_us();
}
}
void PrintHelp_en_us()
{
std::wcout << _T("How to use:") << std::endl;
std::wcout << _T("CLogUtils -options=value") << std::endl;
std::wcout << std::endl;
std::wcout << _T("-p=[number]: Enable pipe mode and specify the pipe name.") << std::endl;
std::wcout << _T("-a=[number]: Number of test output logs.") << std::endl;
std::wcout << _T("-d=[string]: The log file output directory.") << std::endl;
std::wcout << _T("-n=[string]: The log file name prefix.") << std::endl;
std::wcout << _T("-f=[number]: Number of log files.") << std::endl;
std::wcout << _T("-s=[number]: Single log file size(bytes).") << std::endl;
std::wcout << _T("-c=[number]: Number of log caches.") << std::endl;
std::wcout << _T("-t=[number]: Automatically save logs to a file timeout value(seconds).") << std::endl;
std::wcout << _T("-?/-help: Help for CLogSrv.") << std::endl;
}
void PrintHelp_zh_cn()
{
std::wcout << _T("使用方法:") << std::endl;
std::wcout << _T("CLogUtils.exe -选项=值") << std::endl;
std::wcout << std::endl;
std::wcout << _T("-p=[字符串]: 启用管道模式并指定管道名.") << std::endl;
std::wcout << _T("-a=[整数]: 测试输出日志数量.") << std::endl;
std::wcout << _T("-d=[字符串]: 日志输出目录.") << std::endl;
std::wcout << _T("-n=[字符串]: 日志文件名前缀.") << std::endl;
std::wcout << _T("-f=[整数]: 日志文件数量.") << std::endl;
std::wcout << _T("-s=[整数]: 单个日志文件大小(字节).") << std::endl;
std::wcout << _T("-c=[整数]: 日志缓冲数量.") << std::endl;
std::wcout << _T("-t=[整数]: 自动保存超时间隔(秒).") << std::endl;
std::wcout << _T("-?/-help: 日志单元帮助.") << std::endl;
}