业务开发中,对于一些异常情况往往需要日志记录。记录日志这件事本身就是把内容写入文件,实现并不复杂。结合本人实际工作需要,并结合所学知识,进一步扩展了,实现了日志常用功能,目的是使用简便快捷, 直接包含头文件和源文件即可使用。
功能:
使用示例
调用: 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
在封装一个日志库之前,我们需要对WINDOW
的一些API有一点点的了解和学习。
通过 GetModuleFileName()
可以得到获取exe(dll)可执行文件的绝对路径。函数原型如下所示:
DWORD WINAPI GetModuleFileName(
_In_opt_ HMODULE hModule, //应用程序或DLL实例句柄,NULL则为获取当前程序可执行文件路径名
_Out_ LPTSTR lpFilename, //接收路径的字符串缓冲区
_In_ DWORD nSize //接收路径的字符缓冲区的大小
);
通过CreateDirectory()
可以创建一个新的目录。函数原型如下:
BOOL CreateDirectory(
LPCTSTR lpPathName,
LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
参数:
GetLastError()
函数。注意,这个函数不是递归的。它可以在一个路径中创建唯一的最终目录。也就是说,如果父目录或中间目录不存在,该函数将失败并显示错误消息ERROR_PATH_NOT_FOUND
。
通常来说,我们也可以先判断该目录是否已经存在,例如。业务上,我们通常用递归的方式,来创建级联子目录。
if (!::PathIsDirectory(str))
{
::CreateDirectory(str, NULL);
}
在说GetLocalTime()
函数之前,我们先认识了解下WINDOWS系统中的系统时间,可以调用GetSystemTime()
函数获取系统的当前日期和时间,返回的系统时间以协调世界时(Coordinated Universal Time,UTC)
表示∶
VoID WINAPl GetSystemTime(_Out_LPSYSTEMTIME lpSystemTime);
// SYSTEMTIME结构
typedef struct _SYSTEMTIME
{
WORD wYear; //年,范围1601~30827
WORD wMonth;//月,范围1~12
WORD wDayOfWeek;//星期,范围0~6
WORD wDay;//日,范围1~31
WORD wHour;//时,范围0~23
WORD wMinute;//分,范围0~59
WORD wSecond; //秒,范围0~59
WORD wMilliseconds; //毫秒,范围0~999
}SYSTEMTIME,*PSYSTEMTIME,*LPSYSTEMTIME;
协调世界时又称世界统一时间、世界标准时间、国际协调时间,是最主要的世界时间标准。其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。如果本地时间比UTC时间快,例如新加坡、马来西亚、澳大利亚西部的时间比UTC快8h,就会写作UTC+8,俗称东8区
相反,如果本地时间比UTC时间慢,例如夏威夷的时间比UTC时间慢10小时,就会写作UTC-10,俗称西10区
。
GetLocalTime()
函数也可以获取系统的当前日期和时间,该函数会根据计算机的时区计算当地时间。对于中国大陆来说,该函数获取的时间比GetSystemTime()
函数获取的时间快8小时
GetSystemTime()
和GetLocalTime()
函数所获取到的时间值的准确性完全取决于用户是否设置了正确的时区以及是否在本机上设置正确的时间,打开控制面板→日期和时间,可以更改日期时间和时区。
两个函数的区别不言而喻,假设SYSTEMTIME结构初始化为"2020年10月1号星期四中午12点"
:
sYSTEMTIME st =
{
2020,//年,范围1601~30827
10,//月,范围1~12
3,//星期,范围0~6
1,//日,范围1~31
12,//时,范围0~23
0,//分,范围0~59
0,//秒,范围0~59
0 //毫秒,范围0~999
};
调用SetSystemTime(&st);
桌面右下角时间显示为2020年10月1号星期四20点
调用SetLocalTime(&st);
桌面右下角时间显示为2020年10月1号星期四12点
注意∶调用SetSystemTime(&st),本机显示的始终是本地时间,因此是(中午12点+8小时)等于20点。
看一下时间单位 s(秒)、ms(毫秒)、us (微秒)、ns(纳秒),ps(皮秒)的关系∶1s = 1000 ms,1 ms = 1000 us,1 us = 1000 ns,1 ns = 1000 ps。还有更小的时间单位,不过一般用不到。
经过笔者的描述,相信你对WINDOW系统中的时间一定有了更多深入的认识了。
通常我们生成日志文件,都是用时间戳,转为日期字符串作为日志文件名的,那么我们需要知道怎么获取当前系统的时间戳呢?WIN32
给我们提供了GetSystemTimeAsFileTime()
通过这个函数,我们获取当前系统时间的FILETIME结构体。然后,通过将FILETIME结构体的dwLowDateTime
和dwHighDateTime
字段合并为一个64位整数,得到一个表示时间的大整数。最后,将这个大整数减去116444736000000000
(这是1601年1月1日到1970年1月1日的时间差),再除以10000
(将单位从100纳秒转换为毫秒),就得到了ntp时间戳(13位时间戳,精确到毫秒)
。 如果需要10位时间戳,则不再是除以10000
了,而是除以10000000
。 如果你听得雨里雾里,这里请忽略文字,直接看代码。
int64_t GetCurrentTimestamp()
{
int64_t timeStamp = 0;
(void)::GetSystemTimeAsFileTime((FILETIME*)&timeStamp); //方式1,int64_t强转
return (timeStamp - 116444736000000000) / 10000;
}
int _tmain() {
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
ULARGE_INTEGER uli;
uli.LowPart = ft.dwLowDateTime;
uli.HighPart = ft.dwHighDateTime; //方式2,合并成64位整数,推荐!
std::cout << "当前系统的时间戳: " << (uli.QuadPart - 116444736000000000) /10000 << std::endl;
std::cout << "当前时间戳 " << GetCurrentTimestamp() << std::endl;
return 0;
}
这里笔者再次说明几个重点
10000
转为秒 需要除以10000000
是这么来的!WIN32中,函数FileTimeToLocalTime()
用来将文件时间格式转换为本地文件时间:
1 BOOL WINAPI FileTimeToLocalFileTime
(
__in const FILETIME* lpFileTime,//文件时间
__out LPFILETIME lpLocalFileTime//本地文件时间
);
进而,可以通过函数FileTimeToSystemTime()
用来将文件时间格式转换为标准系统时间格式:
BOOL WINAPI FileTimeToSystemTime
(
__in const FILETIME *lpFileTime, //本地文件时间
__out LPSYSTEMTIME lpSystemTime //系统时间
);
最终会得到如下这样的一个封装把时间戳转为日期字符串。
_tstring CLogUtils::TimestampToString(int64_t timestamp, const _tstring& strFormat)
{
TCHAR szBuf[MAX_PATH] = { 0 };
SYSTEMTIME st = { 0 };
FILETIME ftFile = { 0 };
FILETIME ftLocal = { 0 };
//ntp时间戳 转为 100纳秒的单位 再加上 1601年1月1日到1970年1月1日的时间差 就得到了UTC时间戳了,也就是WIN32中认定的时间戳
timestamp = timestamp * 10000 + 116444736000000000;
ftFile.dwLowDateTime = timestamp & 0xFFFFFFFF;
ftFile.dwHighDateTime = timestamp >> 32; //这里的运算时MSDN推荐的。
//文件格式时间转为本地文件时间
::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;
}
内存的管理,人家Win32内部也有自己的一套规则, 业务开发中,我们通常需要,在堆中分配和释放内存块。
HeapAlloc()
函数用于从堆中分配一块内存
LPVOID WINAPI HeapAlloc(
_ln_ HANDLE hHeap,//堆的句柄,从中分配内存,由HeapCreate或GetProcessHeap函数返回
_ln_ DWORD dwFlags,//堆分配选项
_ln_SIZE_T dwBytes //要分配的内存块大小,以字节为单位
);
常量宏 | 含义 |
---|---|
HEAP ZERO_MEMORY | 将分配的内存块初始化为0,即清零操作,在默认情况下,从堆中分配(HeapAlloc)或重新分配(HeapReAlloc)内存块失败会返回NULL,指定该标志后,如果分配失败则会抛出一个异常以通知应用程序有错误发生。 |
HEAP_GENERATE_EXCEPTIONS | 如果希望堆中所有内存分配(HeapAlloc)或重新分配(HeapReAlloc)函数失败时都抛出一个异常,则应该在调用HeapCreate函数创建堆时为flOptions参数指定该标志。如果调用HeapCreate函数时指定了HEAP_GENERATE_EXCEPTIONS标志,则以后在该堆中的所有内存分配(HeapAlloc或重新分配(HeapReAlloc) 函数失败时都会抛出一个异常。如果调用HeapCreate函数时未指定HEAP_GENERATE_EXCEPTIONS标志,则可以在这里使用该标志单独指定对本次分配操作失败抛出一个异常 |
HEAP_NO_SERIALIZE | HEAP_NO_SERIALIZE如果当初调用HeapCreate函数时指定了HEAP_NO_SERIALIZE标志,则后续在该堆中的所有内存分配或释放操作都不进行独占检测。如果当初调用HeapCreate函数时没有指定HEAP_NO_SERIALIZE标志,则可以在这里使用该标志单独指定不对本次分配操作进行独占检测。在进程的默认堆中分配内存时,绝对不要使用这个标志,否则可能会破坏数据,因为进程中的其他线程可能会在同一时刻访问堆 |
如果函数执行成功,则返回值是指向已分配内存块的指针。如果函数执行失败并且没有指定
HEAP_GENERATE_EXCEPTIONS标志,则返回值为NULL。如果函数执行失败并且已经指定
HEAP GENERATE EXCEPTIONS标志,则该函数可能会生成表所表示的异常
异常代码宏 | 含义 |
---|---|
STATUS_NO_MEMORY | 由于缺少可用内存或堆损坏导致分配操作失败 |
STATUS_ACCESS_VIOLATION | 由于堆损坏或不正确的函数参数导致分配操作失败 |
当从堆中分配内存时,系统会执行以下操作步骤。
例如下面的代码,创建一个不限制最大大小的私有堆,然后从堆中分配1024字节的内存∶
LPVOID lp = NULL;
hHeap = HeapCreate(0,0,O);
lp = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,1024);//分配1024字节的内存
有时候程序可能需要调整已分配内存块的大小,程序一开始可能分配一块大于实际需要的内存块,在把需要的数据都放到这块内存中以后再减小内存块的大小;也可能一开始分配的内存块太小,不满足实际需要,这时需要增大内存块的大小。如果需要调整内存块的大小可以调用HeapReAlloc()
函数∶
LPVOID WINAPI HeapReAlloc
(
_ln_ HANDLE hHeap,//堆的句柄,从中重新分配内存
_In_DWORD dwFlags,//堆分配选项
_ln_ LPVOID lpMem,//要调整大小的内存块指针
_In_ SIZE_T dwBytes//要调整到的大小,以字节为单位
);
dwFlags参数指定堆分配选项,可以是下表所示的一个或多个值。
宏常量 | 含义 |
---|---|
HEAP_ ZERO_MEMORY | 如果重新分配的内存块比原来的大,则超出原始大小的部分将初始化为0,但是内存块中原始大小的内容不受影响;如果重新分配的内存块比原来小,则该标志不起作用 |
HEAP_REALLOC_IN_PLACE_ONLY | 在增大内存块时,HeapReAlloc函数可能会在堆内部移动内存块,如果在原内存块地址处无法找到一块连续的满足新分配大小的内存空间,则函数会在其他位置寻找一块足够大的闲置内存空间并把原内存块的内容复制过来,然后函数将返回一个新地址,很明显新地址与原地址不同﹔如果HeapReAlloc函数能够在不移动内存块的前提下使它增大,则函数将返回原内存块的地址。指定HEAP_REALLOC_IN_PLACE_ONLY标志是用来告诉 HeapReAlloc函数不要移动内存块,如果能够在不移动内存块的前提下使它增大,或者要把内存块减小,则HeapReAlloc函数会返回原内存块的地址﹔如果指定了该标志并且无法在不移动内存块的情况下调整内存块的大小,则函数调用将失败。无论哪种情况,原始内存块部分的内容始终保持不变 |
HEAP_GENERATE_EXCEPTIONS | 参见HeapAlloc函数的说明 |
HEAP_NO_SERIALIZE | 参见HeapAlloc函数的说明 |
HeapReAlloc函数的返回值情况与HeapAlloc函数相同。如果函数执行成功,则返回值是指向新内存块的指针。如果指定了HEAP_REALLOC_IN_PLACE_ONLY标志,则新内存块的指针必定与原来的相同,否则它既有可能与原来的指针相同也有可能不同。
比如说,下面图片中的代码。
这段代码调用HeapAlloc函数分配内存块返回的地址为
0x038905A8,接着程序向这块内存中写入字符串,然后调用HeapReAlloc函数增大内存块为8KB,函数返回一个新的内
存块地址Ox038909B0,从内存1窗口可以看到,原内存块的内容复制到了新内存块中。
HeapFree函数用于释放HeapAlloc或HeapReAlloc函数从堆中分配的内存块∶
BOOL WINAPl HeapFree(
_ln_HANDLE hHeap,//要释放其内存块的堆的句柄
_ln_ DwORD dwFlags,//堆释放选项,可以设置为0或HEAP_NO_SERIALIZE
_in_LPVOID lpMem //要释放的内存块的指针
);
一般来说,我们在业务中,通常这样进行内存的分配和释放。
//分配缓冲
lpBuf = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwCchCount * sizeof(TCHAR));
//释放缓冲
if (nullptr != lpBuf)
{
::HeapFree(::GetProcessHeap(), 0, lpBuf);
lpBuf = nullptr;
}
为了满足我们写日志的需求,我们还需要一个存日志文件的目录,存在一种情况是说,用你库的开发给的全路径不是一个目录路径是一个文件路径,亦或这个路径不存在等这些问题要判断后,分情况考虑来进行处理。
WIN32 提供了GetFileAttributesEx()
函数,我们可以给定一个文件路径,可以查到文件的属性,来判断是目录还是文件,还可以判断该全路径表示的文件是不是存在的。
BOOL GetFileAttributesEx
(
LPCSTR lpFileName, //文件名
GET_FILEEX_INFO_LEVELS fInfoLevelld, //需要设置成GetFileExInfoStandard参数
LPVOID lpFileInfomation //将LPVOID的地址赋值给LP WIN32_FILE_ATTRIBUTE_DATA参数
);
参数说明:
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;
}
还可以做到查文件的大小。
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;
}
通常我们使用可变参数,配合格式化字符串。这样日志的内容可以使用%s %d
来注入。使用指向参数列表的指针写入格式化的输出,很方便。
int vsnprintf_s(
char *buffer,
size_t sizeOfBuffer,
size_t count,
const char *format,
va_list argptr
);
函数的参数:
_TRUNCATE
然后,va_start()
函数的用途是在可变参数函数中初始化一个va_list
类型的变量,以便访问函数参数列表中的参数。
void va_start(
va_list arg_ptr,
prev_param
); // (ANSI C89 and later)
函数参数:
一般业务上来说,这么写,就可以得到替换之后值的字符串了。
_tstring CLogUtils::Format(LPCTSTR pstrFormat, ...)
{
_tstring strResult;
va_list args;
va_start(args, pstrFormat);
if (-1 != _vsntprintf_s(lpBuf, dwCchCount, _TRUNCATE, pstrFormat, args))
{
strResult = lpBuf;
break;
}
va_end(args);
return strResult;
}
其他的一些细节,提供读者自行学习,笔者不再累述。
#pragma once
#include
#include
#include
#include
#include
#include
#include
//请在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 USR_STRING_BUFFER_CONTENT (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();
}
#include "CLogUtils.h"
#include
#include
#include
#include
#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();
}
else
{
if (nullptr == m_hEvent)
{
m_hEvent = ::CreateEvent(nullptr, false, false, nullptr);
}
if (nullptr == 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 (nullptr != m_hEvent)
{
m_bStop = true;
::SetEvent(m_hEvent);
m_hEvent = nullptr;
}
if (m_tAutoSaveTask.joinable())
{
m_tAutoSaveTask.join();
}
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;
}
}
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 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;
}
}
#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 USR_STRING_BUFFER_CONTENT
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 USR_STRING_BUFFER_CONTENT
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;
//缓冲字符数在合理范围内则扩大2倍
if (dwFormatBufCch < INT32_MAX)
{
dwFormatBufCch *= 2;
}
else
{
//超限终止处理
break;
}
} 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;
}
}
#include
#include
#include
#include
#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;
}