• Win32 简单日志实现


    简介

    个人实现的轻量化日志助手类, 实现了日志常用功能, 目的是使用简便快捷,  直接包含头文件和源文件即可使用

    功能

    1.可设置日志文件存放路径以及文件名前缀

    2.可设置单个日志文件最大容量, 到限后自动转储到下一个日志文件

    3.可设置日志自动保存, 按照时间间隔或日志缓存条目数量

    4.可设置最多保存的日志文件数量, 超限会自动删除最旧的日志文件

    5.可设置管道模式, 在管道模式下可使用另一个进程来处理日志转存

    日志代码gitee源码

    使用示例

    调用: 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万条 / 秒

    a37af2377fc5484380bb7277c8f1daf4.png

    Release x64 管道模式下速度 5万条 / 秒

    898bf78476e94fcbabc426031f741c1c.png

    配置2:

    CPU: Intel(R) Core(TM) i9-14900K 

    内存: 16GB(6000Mhz) * 2

    Release x64 默认模式下 140万条 / 秒

    6e3d6fa0fe4a4de898711a75c34e4c68.png

    Release x64 管道模式 54万条 / 秒

    1885219fb22647b4b65768ae46a83b32.png

    CLogUtils.h

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. //请在VS项目属性页 -> C/C++ -> 预处理器 中 添加如下宏指定日志根目录, 用于截取源码文件路径相对路径
    10. //LOG_ROOT_DIR=R"($(ProjectDir))"
    11. //效果:
    12. //添加宏之前: 2023-11-22 13:14:39.644 INFO [34688:22188] [D:\Gitee_FlameCyclone\c-log-utils\CLogUtils\CLogUtils\main.cpp:155] [CLogUtilsTest] 1024 FlameCyclone
    13. //添加宏之后: 2023-11-22 12:31:33.45 INFO [20884:7996] [.\main.cpp:133] [CLogUtilsTest] 1024 FlameCyclone
    14. #ifdef _UNICODE
    15. using _tstring = std::wstring;
    16. #else
    17. using _tstring = std::string;
    18. #endif
    19. #pragma warning(disable:4200)
    20. namespace CLogUtils
    21. {
    22. class CNamedPipe;
    23. #define STRING_CONTENT_BUFFER_ENABLE (1) //使用字符串作为日志缓冲
    24. #define LOG_FILE_COUNT (16) //最多日志文件历史数量
    25. #define LOG_TIMEOUT (1000 * 60) //自动保存超时时间(毫秒)
    26. #define LOG_FILE_SIZE (1024 * 1024 * 16) //单个日志文件大小阈值(字节)
    27. #define LOG_BUF_COUNT (10000) //日志缓冲大小阈值
    28. #define LOG_INFO(format, ...)\
    29. GetInstance().Logging(_T(" INFO"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
    30. #ifdef _DEBUG
    31. #define LOG_DEBUG(format, ...)\
    32. GetInstance().Logging(_T("DEBUG"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
    33. #else
    34. #define LOG_DEBUG(format, ...)\
    35. GetInstance().DoNothing();
    36. #endif
    37. #define LOG_WARN(format, ...)\
    38. GetInstance().Logging(_T(" WARN"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
    39. #define LOG_ERROR(format, ...)\
    40. GetInstance().Logging(_T("ERROR"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
    41. class CLogHelper
    42. {
    43. public:
    44. #define Info(format, ...)\
    45. Logging(_T(" INFO"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
    46. #ifdef _DEBUG
    47. #define Debug(format, ...)\
    48. Logging(_T("DEBUG"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
    49. #else
    50. #define Debug(format, ...)\
    51. DoNothing();
    52. #endif
    53. #define Warn(format, ...)\
    54. Logging(_T(" WARN"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
    55. #define Error(format, ...)\
    56. Logging(_T("ERROR"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
    57. public:
    58. //默认构造
    59. CLogHelper();
    60. //删除拷贝构造与赋值重载
    61. CLogHelper(const CLogHelper&) = delete;
    62. //删除赋值重载
    63. CLogHelper& operator = (const CLogHelper&) = delete;
    64. //析构
    65. ~CLogHelper();
    66. //
    67. // @brief: 初始化
    68. // @param: bPipeMode 管道模式 true: 管道模式 false: 默认模式
    69. // @param: strPipeName 管道名
    70. // @param: strDir 存放目录
    71. // @param: strPreName 文件名前缀
    72. // @param: nFileSize 单个日志文件大小阈值(字节), 到达限制转储到下一个文件
    73. // @param: nTempCount 日志缓存条目限制(到达限制转储到文件)
    74. // @param: nTimeout 自动存储时间间隔(毫秒), 到达限制转储到文件
    75. // @param: nFileCount 日志文件数量限制, 到达限制删除最旧的日志文件
    76. // @ret: bool 执行结果
    77. bool Initialize(
    78. bool bPipeMode = false,
    79. const _tstring& strPipeName = _T(""),
    80. const _tstring& strDir = _T(""),
    81. const _tstring& strPreName = _T(""),
    82. DWORD nFileSize = LOG_FILE_SIZE,
    83. DWORD nFileCount = LOG_FILE_COUNT,
    84. DWORD nTempCount = LOG_BUF_COUNT,
    85. DWORD nTimeout = LOG_TIMEOUT
    86. );
    87. //
    88. // @brief: 反初始化
    89. // @ret: void
    90. void Uninitialize();
    91. //
    92. // @brief: 记录一条日志
    93. // @param: pstrLevel 日志等级
    94. // @param: pstrFile 源码文件
    95. // @param: pstrFunc 源码函数
    96. // @param: nLine 行数
    97. // @param: pstrFormat 格式化字符串
    98. // @param: ... 可变参数
    99. // @ret: void
    100. void Logging(
    101. LPCTSTR pstrLevel,
    102. LPCTSTR pstrFile,
    103. LPCTSTR pstrFunc,
    104. UINT nLine,
    105. LPCTSTR pstrFormat,
    106. ...
    107. );
    108. //
    109. // @brief: 清空已经存储的日志文件
    110. // @ret: void
    111. void Clear();
    112. //
    113. // @brief: 刷新日志缓冲(输出日志到文件)
    114. // @ret: bool 执行结果
    115. bool FlushBuffers();
    116. //
    117. // @brief: 啥也不干
    118. // @ret: void
    119. void DoNothing();
    120. private:
    121. //
    122. // @brief: 获取目录下文件路径
    123. // @ret: std::vector<_tstring> 日志文件列表
    124. std::map<int64_t, _tstring> _GetLogFileList(const _tstring& strDir);
    125. //
    126. // @brief: 调整日志文件数量
    127. // @param: void
    128. // @ret: void
    129. void _AdjustLogFile();
    130. //
    131. // @brief: 通过管道处理日志
    132. // @ret: bool 执行结果
    133. bool _ProcessByPipe();
    134. //
    135. // @brief: 超时保存处理
    136. // @ret: bool 执行结果
    137. bool _ProcessTimeoutSave();
    138. //
    139. // @brief: 管道方式记录日志
    140. // @ret: bool 执行结果
    141. bool _LoggingByPipe(const _tstring& strLogContent);
    142. //
    143. // @brief: 刷新日志缓冲(输出日志到文件)
    144. // @ret: bool 执行结果
    145. bool _FlushLogBuffers();
    146. //
    147. // @brief: 记录日志
    148. // @ret: bool 执行结果
    149. bool _LoggingContent(const _tstring& strLogContent);
    150. //
    151. // @brief: 初始化
    152. // @param: void
    153. // @ret: bool 执行结果
    154. bool _Initialize();
    155. //
    156. // @brief: 取消初始化
    157. // @param: void
    158. // @ret: void
    159. void _Uninitialize();
    160. //
    161. // @brief: 初始化日志文件
    162. // @param: void
    163. // @ret: int 日志文件索引
    164. void _InitLogFile();
    165. //
    166. // @brief: 生成日志转储文件路径
    167. // @param: void
    168. // @ret: void
    169. void _GenerateLogFilePath();
    170. //
    171. // @brief: 获取默认日志管道名
    172. // @param: void
    173. // @ret: _tstring 管道名
    174. _tstring _GetDefaultPipeName() const;
    175. private:
    176. std::vector<_tstring> m_logList; //日志记录缓冲
    177. std::map<int64_t, _tstring> m_logFileList; //日志文件记录, 按照时间戳排序
    178. std::thread m_tAutoSaveTask; //超时自动保存任务线程
    179. std::thread m_tPipeRecvTask; //管道接收任务线程
    180. std::mutex m_Lock; //线程安全锁
    181. CNamedPipe* m_pRecvPipe = nullptr; //日志接收管道
    182. CNamedPipe* m_pSendPipe = nullptr; //日志发送管道
    183. HANDLE m_hEvent = nullptr; //通知事件, 使用自动转储的超时等待
    184. HANDLE m_hFile = INVALID_HANDLE_VALUE; //文件句柄, 日志文件写入使用
    185. int64_t m_nFileTimetamp = 0; //日志文件时间戳
    186. _tstring m_strSaveDir; //日志存放目录
    187. _tstring m_strSaveName; //日志文件名
    188. _tstring m_strFilePath; //当前日志文件路径
    189. _tstring m_strLogContent; //日志内容
    190. _tstring m_strPipeName; //管道名
    191. bool m_bStop = false; //停止标记
    192. bool m_bFirst = false; //首次记录日志标记
    193. bool m_bPipeMode = false; //管道模式
    194. DWORD m_nFileSize = 0; //文件大小限制(到达限制则转储到文件)
    195. DWORD m_nTempCount = 0; //缓存条目限制(到达限制则转储到文件)
    196. DWORD m_nFileCount = 0; //历史文件数量限制(超限则删除旧文件)
    197. DWORD m_nTimeout = 0; //自动保存超时限制(超时则转储到文件)
    198. DWORD m_nCurFileSize = 0; //日志文件统计
    199. DWORD m_nNextItemSize = 0; //下一条日志大小
    200. DWORD m_nLogItemCount = 0; //日志缓冲统计
    201. };
    202. class CNamedPipe
    203. {
    204. public:
    205. CNamedPipe();
    206. ~CNamedPipe();
    207. CNamedPipe(const CNamedPipe& r) = delete;
    208. CNamedPipe& operator = (const CNamedPipe& r) = delete;
    209. //
    210. // @brief: 创建命名管道
    211. // @param: lpName 管道名
    212. // @ret: bool true: 创建成功 false: 创建失败
    213. bool Create(LPCTSTR lpName);
    214. //
    215. // @brief: 等待客户端连接命名管道
    216. // @param: nTimeOut 超时等待(毫秒)
    217. // @ret: bool true: 连接成功 false: 连接失败
    218. bool WaitConnect(DWORD nTimeOut = INFINITE);
    219. //
    220. // @brief: 关闭由Create 创建的管道
    221. // @param: void
    222. // @ret: bool true: 关闭 成功 false: 关闭 失败
    223. bool Disconnect();
    224. //
    225. // @brief: 打开已存在的命名管道
    226. // @param: lpName 管道名
    227. // @ret: bool true: 打开成功 false: 打开失败
    228. bool Open(LPCTSTR lpName, DWORD nTimeOut = INFINITE);
    229. //
    230. // @brief: 管道是否有效
    231. // @param: void
    232. // @ret: bool true: 可用 false: 无效
    233. bool IsValid();
    234. //
    235. // @brief: 关闭管道
    236. // @param: void
    237. // @ret: void
    238. void Close(void);
    239. //
    240. // @brief: 从读取管道数据
    241. // @param: lpData 数据存放缓冲
    242. // @param: nSize 缓冲大小(字节)
    243. // @param: lpBytesRead 指向实际读取大小(字节)的指针
    244. // @param: nTimeOut 读取超时(毫秒)
    245. // @ret: bool true: 读取成功 false: 读取失败
    246. bool Read(LPVOID lpData, DWORD nSize, LPDWORD lpBytesRead = nullptr, DWORD nTimeOut = INFINITE);
    247. //
    248. // @brief: 向管道写入数据
    249. // @param: lpData 写入数据指针
    250. // @param: nSize 写入数据大小(字节)
    251. // @param: lpBytesWritten 指向实际写入大小(字节)的指针
    252. // @param: nTimeOut 写入超时(毫秒)
    253. // @ret: bool true: 写入成功 false: 写入失败
    254. bool Write(LPCVOID lpData, DWORD nSize, LPDWORD lpBytesWritten = nullptr, DWORD nTimeOut = INFINITE);
    255. private:
    256. //
    257. // @brief: 初始化对象占用
    258. // @param: void
    259. // @ret: void
    260. bool Initialize();
    261. //
    262. // @brief: 释放对象占用
    263. // @param: void
    264. // @ret: void
    265. void Uninitialize();
    266. private:
    267. HANDLE m_hNamedPipe = INVALID_HANDLE_VALUE;
    268. HANDLE m_hReadEvent = NULL;
    269. HANDLE m_hWriteEvent = NULL;
    270. HANDLE m_hConnectEvent = NULL;
    271. LPVOID m_pBuffer = nullptr;
    272. bool m_bInit = false;
    273. bool m_bConnected = false;
    274. };
    275. //
    276. // @brief: 格式化字符串
    277. // @param: void
    278. // @ret: bool 执行结果
    279. _tstring Format(LPCTSTR pstrFormat, ...);
    280. //
    281. // @brief: 获取当前进程完全路径
    282. // @ret: 当前进程完全路径 如 D:\Software\HxDPortableSetup.exe
    283. _tstring GetCurrentModulePath();
    284. //
    285. // @brief: 获取当前进程所在目录
    286. // @ret: 当前进程所在目录 如 D:\Software
    287. _tstring GetCurrentModuleDir();
    288. //
    289. // @brief: 获取当前进程名
    290. // @ret: 当前进程名 如 HxDPortableSetup.exe
    291. _tstring GetCurrentModuleName(bool bHasExt = false);
    292. //
    293. // @brief: 获取文件所在文件夹
    294. // @param: strPath 文件名, 如: D:\Software\HxDPortableSetup.exe
    295. // @ret: 文件夹 如 D:\Software
    296. _tstring GetFileDir(const _tstring& strPath);
    297. //
    298. // @brief: 获取文件名
    299. // @param: strPath 文件名, 如: D:\Software\HxDPortableSetup.exe
    300. // @param: bHasExt 是否包含扩展名
    301. // @ret: 文件夹 如 HxDPortableSetup
    302. _tstring GetFileName(const _tstring& strPath, bool bHasExt = false);
    303. //
    304. // @brief: 检查文件是否存在
    305. // @param: strPath 文件名, 如: D:\Software\HxDPortableSetup.exe
    306. // @ret: 是否存在 存在返回 true
    307. bool IsDirectory(const _tstring& strPath);
    308. //
    309. // @brief: 创建目录(递归)
    310. // @param: strPath 路径
    311. // @ret: 成功返回true
    312. bool CreateDir(const _tstring& strPath);
    313. //
    314. // @brief: 删除文件
    315. // @param: strPath 路径
    316. // @ret: 成功返回true
    317. bool DeleteArchive(const _tstring& strPath);
    318. //
    319. // @brief: 获取当前时间戳字符串
    320. // @param: void
    321. // @ret: _tstring 时间戳字符串 如: 2023-10-11 17:43:00.617
    322. _tstring GetCurrentTimeString();
    323. //
    324. // @brief: 获取当前时间戳
    325. // @param: void
    326. // @ret: 时间戳(单位: 毫秒) 如: 1697017380617
    327. int64_t GetCurrentTimestamp();
    328. //
    329. // @brief: 时间戳转字符串
    330. // @param: strFormat 格式化字符串 如: "%04d-%02d-%02d %02d:%02d:%02d.03%d"
    331. // @param: timestamp 时间戳 如: 1697017380617
    332. // @ret: 时间字符串 如: 2023-10-11 17:43:00.617
    333. _tstring TimestampToString(
    334. int64_t timestamp = 0,
    335. const _tstring& strFormat = _T("%04d-%02d-%02d %02d:%02d:%02d.03%d")
    336. );
    337. //
    338. // @brief: 获取文件大小
    339. // @param: strPath 路径
    340. // @ret: 文件大小
    341. unsigned long long GetFileSize(const _tstring& strPath);
    342. CLogHelper& GetInstance();
    343. }

    CLogUtils.cpp

    1. #include "CLogUtils.h"
    2. #include
    3. #include
    4. #include
    5. #include
    6. #pragma comment(lib, "Shlwapi.lib")
    7. namespace CLogUtils
    8. {
    9. #define DEFAULT_LOG_FILE_COUNT_MIN (4) //默认最少日志文件历史数量
    10. #define DEFAULT_LOG_TIMEOUT_MIN (1000 * 5) //默认自动保存超时时间(毫秒)
    11. #define DEFAULT_LOG_FILE_SIZE_MIN (1024 * 1024 * 1) //默认单个日志文件大小阈值(字节)
    12. #define DEFAULT_LOG_BUF_COUNT_MIN (256) //默认日志缓冲数量阈值
    13. #define DEFAULT_LOG_FILE_COUNT_MAX (256) //默认最多日志文件历史数量
    14. #define DEFAULT_LOG_TIMEOUT_MAX (1000 * 60) //默认自动保存超时时间(毫秒)
    15. #define DEFAULT_LOG_FILE_SIZE_MAX (1024 * 1024 * 256) //默认单个日志文件大小阈值(字节)
    16. #define DEFAULT_LOG_BUF_COUNT_MAX (1024 * 4) //默认日志缓冲数量阈值
    17. #define PIPE_NAME_PREFIX TEXT(R"(\\.\pipe\)") //管道前缀名
    18. #define PIPE_MAX_TIMEOUT (3000) //管道打开超时
    19. #define PIPE_BUF_MAX_SIZE (1024 * 1024) //管道发送缓冲大小(字节)
    20. #define PIPE_MAX_CONNECT (1) //管道最大实例数量
    21. typedef struct _PIPE_DATA
    22. {
    23. DWORD dwSize = 0;
    24. BYTE data[0];
    25. }PIPE_DATA, * PPIPE_DATA;
    26. //全局实例构造
    27. static CLogHelper g_Instance;
    28. CLogHelper::CLogHelper() :
    29. m_bPipeMode(false),
    30. m_strPipeName(_T("")),
    31. m_strSaveDir(_T("")),
    32. m_strSaveName(_T("")),
    33. m_nFileSize(LOG_FILE_SIZE),
    34. m_nTempCount(LOG_BUF_COUNT),
    35. m_nTimeout(LOG_TIMEOUT),
    36. m_nFileCount(LOG_FILE_COUNT)
    37. {
    38. }
    39. CLogHelper::~CLogHelper()
    40. {
    41. this->Uninitialize();
    42. }
    43. void CLogHelper::DoNothing()
    44. {
    45. }
    46. bool CLogHelper::Initialize(
    47. bool bPipeMode/* = false*/,
    48. const _tstring& strPipeName/* = _T("")*/,
    49. const _tstring& strDir/* = _T("")*/,
    50. const _tstring& strPreName/* = _T("")*/,
    51. DWORD nFileSize/* = LOG_FILE_SIZE*/,
    52. DWORD nFileCount/* = LOG_FILE_COUNT*/,
    53. DWORD nTmpCount/* = LOG_BUF_COUNT*/,
    54. DWORD nTimeout/* = LOG_TIMEOUT*/
    55. )
    56. {
    57. m_bPipeMode = bPipeMode;
    58. m_strPipeName = strPipeName;
    59. m_strSaveDir = strDir;
    60. m_strSaveName = strPreName;
    61. m_nFileSize = nFileSize;
    62. m_nTempCount = nTmpCount;
    63. m_nTimeout = nTimeout;
    64. m_nFileCount = nFileCount;
    65. //最小参数限制
    66. if (m_nFileSize < DEFAULT_LOG_FILE_SIZE_MIN)
    67. {
    68. m_nFileSize = DEFAULT_LOG_FILE_SIZE_MIN;
    69. }
    70. if (m_nFileCount < DEFAULT_LOG_FILE_COUNT_MIN)
    71. {
    72. m_nFileCount = DEFAULT_LOG_FILE_COUNT_MIN;
    73. }
    74. if (m_nTempCount < DEFAULT_LOG_BUF_COUNT_MIN)
    75. {
    76. m_nTempCount = DEFAULT_LOG_BUF_COUNT_MIN;
    77. }
    78. if (m_nTimeout < DEFAULT_LOG_TIMEOUT_MIN)
    79. {
    80. m_nTimeout = DEFAULT_LOG_TIMEOUT_MIN;
    81. }
    82. //最大参数限制
    83. if (m_nFileSize > DEFAULT_LOG_FILE_SIZE_MAX)
    84. {
    85. m_nFileSize = DEFAULT_LOG_FILE_SIZE_MAX;
    86. }
    87. if (m_nFileCount > DEFAULT_LOG_FILE_COUNT_MAX)
    88. {
    89. m_nFileCount = DEFAULT_LOG_FILE_COUNT_MAX;
    90. }
    91. if (m_nTempCount > DEFAULT_LOG_BUF_COUNT_MAX)
    92. {
    93. m_nTempCount = DEFAULT_LOG_BUF_COUNT_MAX;
    94. }
    95. if (m_nTimeout > DEFAULT_LOG_TIMEOUT_MAX)
    96. {
    97. m_nTimeout = DEFAULT_LOG_TIMEOUT_MAX;
    98. }
    99. //默认目录为当前进程目录
    100. if (m_strSaveDir.empty())
    101. {
    102. m_strSaveDir = GetCurrentModuleDir();
    103. }
    104. //默认文件名为当前进程名
    105. if (m_strSaveName.empty())
    106. {
    107. m_strSaveName = GetCurrentModuleName(true);
    108. }
    109. //目录不存在就创建目录
    110. if (!IsDirectory(m_strSaveDir))
    111. {
    112. CreateDir(m_strSaveDir);
    113. }
    114. if (m_strPipeName.empty())
    115. {
    116. m_strPipeName = _GetDefaultPipeName();
    117. }
    118. return this->_Initialize();
    119. }
    120. void CLogHelper::Uninitialize()
    121. {
    122. _Uninitialize();
    123. }
    124. bool CLogHelper::_Initialize()
    125. {
    126. _Uninitialize();
    127. // 管道模式
    128. if (m_bPipeMode)
    129. {
    130. m_pRecvPipe = new (std::nothrow) CNamedPipe;
    131. m_pSendPipe = new (std::nothrow) CNamedPipe;
    132. if (nullptr == m_pRecvPipe)
    133. {
    134. return false;
    135. }
    136. if (nullptr == m_pSendPipe)
    137. {
    138. return false;
    139. }
    140. std::promise<bool> m;
    141. std::future<bool> p = m.get_future();
    142. m_tPipeRecvTask = std::move(
    143. std::thread([this, &m]() -> void
    144. {
    145. m.set_value(true);
    146. _ProcessByPipe();
    147. }
    148. )
    149. );
    150. //等待线程启动完毕
    151. p.get();
    152. }
    153. // 超时自动保存任务
    154. {
    155. if (NULL == m_hEvent)
    156. {
    157. m_hEvent = ::CreateEvent(nullptr, false, false, nullptr);
    158. }
    159. if (NULL == m_hEvent)
    160. {
    161. return false;
    162. }
    163. std::promise<bool> m;
    164. std::future<bool> p = m.get_future();
    165. m_tAutoSaveTask = std::move(
    166. std::thread([this, &m]()
    167. {
    168. m.set_value(true);
    169. _ProcessTimeoutSave();
    170. }
    171. )
    172. );
    173. //等待线程启动完毕
    174. p.get();
    175. }
    176. return true;
    177. }
    178. void CLogHelper::_Uninitialize()
    179. {
    180. if (!m_logList.empty() || !m_strLogContent.empty())
    181. {
    182. FlushBuffers();
    183. }
    184. if (INVALID_HANDLE_VALUE != m_hFile)
    185. {
    186. ::CloseHandle(m_hFile);
    187. m_hFile = INVALID_HANDLE_VALUE;
    188. }
    189. if (NULL != m_hEvent)
    190. {
    191. m_bStop = true;
    192. ::SetEvent(m_hEvent);
    193. }
    194. if (m_tAutoSaveTask.joinable())
    195. {
    196. m_tAutoSaveTask.join();
    197. }
    198. if (NULL != m_hEvent)
    199. {
    200. ::CloseHandle(m_hEvent);
    201. m_hEvent = NULL;
    202. }
    203. if (m_bPipeMode)
    204. {
    205. if (m_pRecvPipe)
    206. {
    207. m_pRecvPipe->Close();
    208. }
    209. if (m_pSendPipe)
    210. {
    211. m_pSendPipe->Close();
    212. }
    213. if (m_tPipeRecvTask.joinable())
    214. {
    215. m_tPipeRecvTask.join();
    216. }
    217. }
    218. if (nullptr != m_pRecvPipe)
    219. {
    220. delete m_pRecvPipe;
    221. m_pRecvPipe = nullptr;
    222. }
    223. if (nullptr != m_pSendPipe)
    224. {
    225. delete m_pSendPipe;
    226. m_pSendPipe = nullptr;
    227. }
    228. m_bFirst = false;
    229. m_bStop = false;
    230. }
    231. bool CLogHelper::_LoggingByPipe(const _tstring& strLogContent)
    232. {
    233. bool bSuccess = false;
    234. if (nullptr == m_pSendPipe)
    235. {
    236. return false;
    237. }
    238. if (!m_pSendPipe->IsValid())
    239. {
    240. if (!m_pSendPipe->Open(m_strPipeName.c_str(), 1000))
    241. {
    242. return false;
    243. }
    244. }
    245. //写入日志内容到管道, 交给另一端处理(可用跨进程)
    246. bSuccess = m_pSendPipe->Write(strLogContent.c_str(), (DWORD)((strLogContent.size() + 1) * sizeof(TCHAR)));
    247. if (!bSuccess)
    248. {
    249. m_pSendPipe->Close();
    250. }
    251. return bSuccess;
    252. }
    253. bool CLogHelper::_FlushLogBuffers()
    254. {
    255. DWORD dwNumberOfBytesWrite = 0;
    256. bool bSuccess = false;
    257. if (INVALID_HANDLE_VALUE == m_hFile)
    258. {
    259. return false;
    260. }
    261. #if STRING_CONTENT_BUFFER_ENABLE
    262. //没有需要写入的日志
    263. if (m_strLogContent.empty())
    264. {
    265. return true;
    266. }
    267. bSuccess = ::WriteFile(m_hFile, m_strLogContent.c_str(), (DWORD)(m_strLogContent.size() * sizeof(TCHAR)), &dwNumberOfBytesWrite, NULL);
    268. m_strLogContent.clear();
    269. #else
    270. //没有需要写入的日志
    271. if (m_logList.empty())
    272. {
    273. return true;
    274. }
    275. for (const auto& item : m_logList)
    276. {
    277. bSuccess = ::WriteFile(m_hFile, item.c_str(), (DWORD)(item.size() * sizeof(TCHAR)), &dwNumberOfBytesWrite, NULL);
    278. if (!bSuccess)
    279. {
    280. break;
    281. }
    282. }
    283. #endif
    284. return bSuccess;
    285. }
    286. bool CLogHelper::_LoggingContent(const _tstring& strLogContent)
    287. {
    288. //获取单行日志内容 + 固定前缀内容 + 真实内容
    289. m_nNextItemSize = (DWORD)(strLogContent.size() * sizeof(TCHAR));
    290. std::lock_guard lock(m_Lock);
    291. //首次启动时, 重置大小统计
    292. if (!m_bFirst)
    293. {
    294. _InitLogFile();
    295. _AdjustLogFile();
    296. m_nCurFileSize = (DWORD)GetFileSize(m_strFilePath);
    297. m_bFirst = true;
    298. }
    299. //单个日志文件大小即将达到或超过阈值则输出到文件, 启用新的文件存储
    300. if ((m_nCurFileSize + m_nNextItemSize) >= m_nFileSize)
    301. {
    302. _FlushLogBuffers();
    303. m_logList.clear();
    304. ::CloseHandle(m_hFile);
    305. m_hFile = INVALID_HANDLE_VALUE;
    306. (void)_GenerateLogFilePath();
    307. m_nCurFileSize = (DWORD)GetFileSize(m_strFilePath);
    308. _AdjustLogFile();
    309. }
    310. //已缓存条目达到阈值则输出到文件
    311. #if STRING_CONTENT_BUFFER_ENABLE
    312. else if (m_nLogItemCount >= m_nTempCount)
    313. {
    314. _FlushLogBuffers();
    315. m_strLogContent.clear();
    316. m_nLogItemCount = 0;
    317. }
    318. #else
    319. else if (m_logList.size() >= m_nTempCount)
    320. {
    321. _FlushLogBuffers();
    322. m_logList.clear();
    323. }
    324. #endif
    325. #if STRING_CONTENT_BUFFER_ENABLE
    326. m_strLogContent += strLogContent;
    327. m_nLogItemCount++;
    328. #else
    329. m_logList.emplace_back(strLogContent);
    330. #endif
    331. //累加统计单个日志文件大小
    332. m_nCurFileSize += m_nNextItemSize;
    333. return true;
    334. }
    335. bool CLogHelper::_ProcessTimeoutSave()
    336. {
    337. while (!m_bStop)
    338. {
    339. DWORD dwWait = ::WaitForSingleObject(m_hEvent, m_nTimeout);
    340. switch (dwWait)
    341. {
    342. case WAIT_TIMEOUT:
    343. case WAIT_OBJECT_0:
    344. {
    345. std::lock_guard lock(m_Lock);
    346. this->_FlushLogBuffers();
    347. m_logList.clear();
    348. }
    349. break;
    350. default:
    351. break;
    352. }
    353. }
    354. return true;
    355. }
    356. bool CLogHelper::_ProcessByPipe()
    357. {
    358. if (m_pRecvPipe->IsValid())
    359. {
    360. return true;
    361. }
    362. if (!m_pRecvPipe->Create(m_strPipeName.c_str()))
    363. {
    364. return false;
    365. }
    366. DWORD dwPipeBufSize = PIPE_BUF_MAX_SIZE;
    367. LPTSTR lpData = nullptr;
    368. bool bFailed = false;
    369. lpData = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwPipeBufSize);
    370. if (nullptr == lpData)
    371. {
    372. return false;
    373. }
    374. while (!m_bStop && !bFailed)
    375. {
    376. if (!m_pRecvPipe->WaitConnect())
    377. {
    378. _tprintf(_T("WaitConnect failed!\r\n"));
    379. break;
    380. }
    381. while (!m_bStop)
    382. {
    383. bool isSuccess = m_pRecvPipe->Read(lpData, dwPipeBufSize);
    384. if (isSuccess)
    385. {
    386. _LoggingContent(lpData);
    387. }
    388. if (ERROR_BROKEN_PIPE == ::GetLastError())
    389. {
    390. m_pRecvPipe->Disconnect();
    391. if (!m_pRecvPipe->WaitConnect())
    392. {
    393. _tprintf(_T("WaitConnect failed!\r\n"));
    394. bFailed = true;
    395. break;
    396. }
    397. }
    398. if (!m_pRecvPipe->IsValid())
    399. {
    400. break;
    401. }
    402. }
    403. }
    404. this->_FlushLogBuffers();
    405. m_logList.clear();
    406. if (nullptr != lpData)
    407. {
    408. ::HeapFree(::GetProcessHeap(), 0, lpData);
    409. lpData = nullptr;
    410. }
    411. return true;
    412. }
    413. void CLogHelper::Logging(
    414. LPCTSTR pstrLevel,
    415. LPCTSTR pstrFile,
    416. LPCTSTR pstrFunc,
    417. UINT nLine,
    418. LPCTSTR pstrFormat,
    419. ...
    420. )
    421. {
    422. if (nullptr == pstrFormat)
    423. {
    424. return;
    425. }
    426. TCHAR szBuf[MAX_PATH] = { 0 };
    427. DWORD dwPid = ::GetCurrentProcessId();
    428. DWORD dwTid = ::GetCurrentThreadId();
    429. _tstring strLogContent;
    430. SYSTEMTIME st = { 0 };
    431. (void)::GetLocalTime(&st);
    432. #ifdef LOG_ROOT_DIR
    433. //日志格式前缀 [时间] [等级] [十进制进程ID:十进制线程ID] [源码位置:行数] [函数名]
    434. //相对路径显示源码文件路径
    435. _TCHAR szRelativePath[MAX_PATH] = { 0 };
    436. if (::PathRelativePathTo(szRelativePath, _T(LOG_ROOT_DIR), FILE_ATTRIBUTE_DIRECTORY, pstrFile, 0))
    437. {
    438. ::StringCchPrintf(
    439. szBuf,
    440. _countof(szBuf),
    441. _T("%04d-%02d-%02d %02d:%02d:%02d.%03d %s [%d:%d] [%s:%d] [%s] "),
    442. st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
    443. pstrLevel,
    444. dwPid,
    445. dwTid,
    446. szRelativePath,
    447. nLine,
    448. pstrFunc
    449. );
    450. }
    451. else
    452. {
    453. ::StringCchPrintf(
    454. szBuf,
    455. _countof(szBuf),
    456. _T("%04d-%02d-%02d %02d:%02d:%02d.%03d %s [%d:%d] [%s:%d] [%s] "),
    457. st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
    458. pstrLevel,
    459. dwPid,
    460. dwTid,
    461. pstrFile,
    462. nLine,
    463. pstrFunc
    464. );
    465. }
    466. #else
    467. //日志格式前缀 [时间] [等级] [十进制进程ID:十进制线程ID] [源码位置:行数] [函数名]
    468. ::StringCchPrintf(
    469. szBuf,
    470. _countof(szBuf),
    471. _T("%04d-%02d-%02d %02d:%02d:%02d.%03d %s [%d:%d] [%s:%d] [%s] "),
    472. st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
    473. pstrLevel,
    474. dwPid,
    475. dwTid,
    476. pstrFile,
    477. nLine,
    478. pstrFunc
    479. );
    480. #endif
    481. strLogContent = szBuf;
    482. va_list args;
    483. va_start(args, pstrFormat);
    484. LPTSTR pFormatBuf = nullptr; //格式化日志缓冲
    485. DWORD dwFormatBufCch = MAX_PATH; //格式化日志缓冲大小
    486. do
    487. {
    488. //分配缓冲
    489. pFormatBuf = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwFormatBufCch * sizeof(TCHAR));
    490. if (nullptr == pFormatBuf)
    491. {
    492. break;
    493. }
    494. //成功则赋值字符串并终止循环
    495. if (-1 != _vsntprintf_s(pFormatBuf, dwFormatBufCch, _TRUNCATE, pstrFormat, args))
    496. {
    497. strLogContent += pFormatBuf;
    498. break;
    499. }
    500. //释放缓冲, 待下次重新分配
    501. ::HeapFree(::GetProcessHeap(), 0, pFormatBuf);
    502. pFormatBuf = nullptr;
    503. //超限终止处理
    504. if (dwFormatBufCch >= INT32_MAX)
    505. {
    506. break;
    507. }
    508. dwFormatBufCch *= 2;
    509. } while (true);
    510. va_end(args);
    511. //释放缓冲
    512. if (nullptr != pFormatBuf)
    513. {
    514. ::HeapFree(::GetProcessHeap(), 0, pFormatBuf);
    515. pFormatBuf = nullptr;
    516. }
    517. strLogContent += _T("\r\n");
    518. if (m_bPipeMode)
    519. {
    520. _LoggingByPipe(strLogContent);
    521. }
    522. else
    523. {
    524. _LoggingContent(strLogContent);
    525. }
    526. }
    527. void CLogHelper::Clear()
    528. {
    529. std::lock_guard lock(m_Lock);
    530. if (INVALID_HANDLE_VALUE != m_hFile)
    531. {
    532. ::CloseHandle(m_hFile);
    533. m_hFile = INVALID_HANDLE_VALUE;
    534. }
    535. m_logFileList = _GetLogFileList(m_strSaveDir);
    536. for (const auto& item : m_logFileList)
    537. {
    538. DeleteArchive(item.second);
    539. }
    540. m_logFileList.clear();
    541. }
    542. bool CLogHelper::FlushBuffers()
    543. {
    544. std::lock_guard lock(m_Lock);
    545. return _FlushLogBuffers();
    546. }
    547. void CLogHelper::_AdjustLogFile()
    548. {
    549. //检查文件数量是否到达阈值, 到达的话删除前面的文件
    550. if (m_logFileList.size() > m_nFileCount)
    551. {
    552. size_t nDeleteCount = m_logFileList.size() - m_nFileCount;
    553. //从日志文件记录列表中删除
    554. for (size_t i = 0; i < nDeleteCount; i++)
    555. {
    556. auto itBegin = m_logFileList.begin();
    557. DeleteArchive(itBegin->second);
    558. m_logFileList.erase(m_logFileList.begin());
    559. }
    560. }
    561. }
    562. void CLogHelper::_InitLogFile()
    563. {
    564. //如果上次最后一个日志文件大小还能存储日志, 就沿用上次的日志文件
    565. m_logFileList = _GetLogFileList(m_strSaveDir);
    566. if (!m_logFileList.empty())
    567. {
    568. auto itLast = m_logFileList.end();
    569. itLast--;
    570. m_nFileTimetamp = itLast->first;
    571. m_strFilePath = itLast->second;
    572. //上次最后一个日志文件不能存储更多日志, 则生成新的日志文件路径
    573. unsigned long long ullFileSize = (DWORD)GetFileSize(m_strFilePath);
    574. if ((ullFileSize + m_nNextItemSize) >= m_nFileSize)
    575. {
    576. (void)_GenerateLogFilePath();
    577. }
    578. else
    579. {
    580. //打开文件以续写日志
    581. m_hFile = CreateFile(
    582. m_strFilePath.c_str(),
    583. GENERIC_WRITE,
    584. FILE_SHARE_READ,
    585. NULL,
    586. OPEN_ALWAYS,
    587. FILE_ATTRIBUTE_NORMAL,
    588. NULL
    589. );
    590. //在文件末尾追加内容
    591. LARGE_INTEGER liDistanceToMove = { 0 };
    592. ::SetFilePointerEx(m_hFile, liDistanceToMove, NULL, FILE_END);
    593. }
    594. }
    595. else
    596. {
    597. (void)_GenerateLogFilePath();
    598. }
    599. }
    600. //
    601. // @brief: 获取日志管道名
    602. // @param: void
    603. // @ret: _tstring 管道名
    604. _tstring CLogHelper::_GetDefaultPipeName() const
    605. {
    606. return Format(_T("%s_%s"), m_strSaveDir.c_str(), m_strSaveName.c_str());
    607. }
    608. void CLogHelper::_GenerateLogFilePath()
    609. {
    610. //得到日志文件时间戳
    611. m_nFileTimetamp = GetCurrentTimestamp();
    612. //得到日志文件路径
    613. m_strFilePath = Format(_T("%s\\%s_%s.log"),
    614. m_strSaveDir.c_str(),
    615. m_strSaveName.c_str(),
    616. TimestampToString(m_nFileTimetamp, _T("%04d-%02d-%02d_%02d-%02d-%02d-%03d")).c_str()
    617. );
    618. //创建一下文件(防止在资源管理器中看不到新的日志文件)
    619. m_hFile = CreateFile(
    620. m_strFilePath.c_str(),
    621. GENERIC_WRITE,
    622. FILE_SHARE_READ,
    623. NULL,
    624. OPEN_ALWAYS,
    625. FILE_ATTRIBUTE_NORMAL,
    626. NULL
    627. );
    628. //在文件末尾追加内容
    629. LARGE_INTEGER liDistanceToMove = { 0 };
    630. ::SetFilePointerEx(m_hFile, liDistanceToMove, NULL, FILE_END);
    631. m_logFileList.insert(std::make_pair(m_nFileTimetamp, m_strFilePath));
    632. }
    633. CNamedPipe::CNamedPipe() :
    634. m_pBuffer(nullptr),
    635. m_hNamedPipe(INVALID_HANDLE_VALUE),
    636. m_hReadEvent(NULL),
    637. m_hWriteEvent(NULL),
    638. m_bConnected(false),
    639. m_bInit(false)
    640. {
    641. //初始化读写缓冲与事件句柄
    642. Initialize();
    643. }
    644. CNamedPipe::~CNamedPipe()
    645. {
    646. //释放读写缓冲与事件句柄
    647. Uninitialize();
    648. }
    649. bool CNamedPipe::Create(LPCTSTR lpName)
    650. {
    651. TCHAR szPipeName[MAX_PATH];
    652. SECURITY_ATTRIBUTES sa = { 0 };
    653. SECURITY_DESCRIPTOR sd = { 0 };
    654. bool isSuccess = false;
    655. sa.nLength = sizeof(sa);
    656. sa.bInheritHandle = FALSE;
    657. sa.lpSecurityDescriptor = &sd;
    658. if (INVALID_HANDLE_VALUE != m_hNamedPipe)
    659. {
    660. return true;
    661. }
    662. //设置权限, 防止低权限进程不能打开高权限进程创建的管道
    663. (void)::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
    664. (void)::SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
    665. (void)::StringCchPrintf(szPipeName, _countof(szPipeName), TEXT("%s%s"), PIPE_NAME_PREFIX, lpName);
    666. do
    667. {
    668. m_hNamedPipe = ::CreateNamedPipe(
    669. szPipeName,
    670. PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
    671. PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
    672. PIPE_MAX_CONNECT,
    673. PIPE_BUF_MAX_SIZE,
    674. PIPE_BUF_MAX_SIZE,
    675. PIPE_MAX_TIMEOUT,
    676. &sa
    677. );
    678. if (INVALID_HANDLE_VALUE == m_hNamedPipe)
    679. {
    680. break;
    681. }
    682. isSuccess = true;
    683. } while (false);
    684. if (!isSuccess)
    685. {
    686. this->Close();
    687. }
    688. return isSuccess;
    689. }
    690. bool CNamedPipe::Open(LPCTSTR lpName, DWORD nTimeOut/* = INFINITE*/)
    691. {
    692. TCHAR szPipeName[MAX_PATH] = { 0 };
    693. bool isSuccess = false;
    694. (void)::StringCchPrintf(szPipeName, _countof(szPipeName), TEXT("%s%s"), PIPE_NAME_PREFIX, lpName);
    695. if (INVALID_HANDLE_VALUE != m_hNamedPipe)
    696. {
    697. return true;
    698. }
    699. ULONGLONG ullCurTick = ::GetTickCount64();
    700. do
    701. {
    702. m_hNamedPipe = ::CreateFile(
    703. szPipeName,
    704. GENERIC_READ | GENERIC_WRITE,
    705. 0,
    706. NULL,
    707. OPEN_EXISTING,
    708. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
    709. NULL
    710. );
    711. //管道句柄有效则终止循环
    712. if (INVALID_HANDLE_VALUE != m_hNamedPipe)
    713. {
    714. isSuccess = true;
    715. break;
    716. }
    717. //若错误原因不是因为所有管道范例都在使用中, 则退出循环
    718. if (ERROR_PIPE_BUSY != ::GetLastError())
    719. {
    720. break;
    721. }
    722. //等待命名管道的实例可用于连接
    723. if (::WaitNamedPipe(szPipeName, 1000))
    724. {
    725. continue;
    726. }
    727. //无限等待则不需要检查超时
    728. if (INFINITE == nTimeOut)
    729. {
    730. continue;
    731. }
    732. //执行操作超时则退出循环
    733. if (::GetTickCount64() - ullCurTick > nTimeOut)
    734. {
    735. break;
    736. }
    737. } while (INVALID_HANDLE_VALUE == m_hNamedPipe);
    738. if (!isSuccess)
    739. {
    740. this->Close();
    741. }
    742. return isSuccess;
    743. }
    744. bool CNamedPipe::WaitConnect(DWORD nTimeOut)
    745. {
    746. OVERLAPPED Overlapped = { 0 };
    747. bool isConnected = false;
    748. if (INVALID_HANDLE_VALUE == m_hNamedPipe)
    749. {
    750. return false;
    751. }
    752. Overlapped.hEvent = m_hConnectEvent;
    753. isConnected = ::ConnectNamedPipe(m_hNamedPipe, &Overlapped);
    754. if (!isConnected)
    755. {
    756. DWORD dwError = ::GetLastError();
    757. //管道关闭中
    758. if (ERROR_NO_DATA == dwError)
    759. {
    760. isConnected = false;
    761. }
    762. else if (ERROR_IO_PENDING == dwError)//操作处于挂起状态
    763. {
    764. if (WAIT_OBJECT_0 == ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
    765. {
    766. isConnected = true;
    767. }
    768. }
    769. else if (ERROR_PIPE_CONNECTED == dwError)//管道已经连接
    770. {
    771. isConnected = true;
    772. }
    773. }
    774. m_bConnected = isConnected;
    775. return isConnected;
    776. }
    777. bool CNamedPipe::Disconnect()
    778. {
    779. if (INVALID_HANDLE_VALUE == m_hNamedPipe)
    780. {
    781. return false;
    782. }
    783. //参数句柄必须由 CreateNamedPipe 函数创建
    784. return ::DisconnectNamedPipe(m_hNamedPipe);
    785. }
    786. void CNamedPipe::Close()
    787. {
    788. if (INVALID_HANDLE_VALUE != m_hNamedPipe)
    789. {
    790. if (m_bConnected)
    791. {
    792. ::FlushFileBuffers(m_hNamedPipe);
    793. ::DisconnectNamedPipe(m_hNamedPipe);
    794. m_bConnected = false;
    795. }
    796. ::CloseHandle(m_hNamedPipe);
    797. m_hNamedPipe = INVALID_HANDLE_VALUE;
    798. }
    799. if (m_hReadEvent)
    800. {
    801. ::SetEvent(m_hReadEvent);
    802. ::CloseHandle(m_hReadEvent);
    803. m_hReadEvent = NULL;
    804. }
    805. if (m_hWriteEvent)
    806. {
    807. ::SetEvent(m_hWriteEvent);
    808. ::CloseHandle(m_hWriteEvent);
    809. m_hWriteEvent = NULL;
    810. }
    811. if (m_hConnectEvent)
    812. {
    813. ::SetEvent(m_hConnectEvent);
    814. ::CloseHandle(m_hConnectEvent);
    815. m_hConnectEvent = NULL;
    816. }
    817. }
    818. bool CNamedPipe::IsValid()
    819. {
    820. return INVALID_HANDLE_VALUE != m_hNamedPipe;
    821. }
    822. bool CNamedPipe::Read(LPVOID lpData, DWORD nSize, LPDWORD lpBytesRead/* = nullptr*/, DWORD nTimeOut)
    823. {
    824. OVERLAPPED Overlapped = { 0 };
    825. Overlapped.hEvent = m_hReadEvent;
    826. DWORD dwBytesTransferred = 0;
    827. bool isSuccess = false;
    828. if (nullptr == m_pBuffer ||
    829. nullptr == lpData ||
    830. 0 == nSize ||
    831. nSize > PIPE_BUF_MAX_SIZE
    832. )
    833. {
    834. return false;
    835. }
    836. PPIPE_DATA pData = reinterpret_cast(m_pBuffer);
    837. if (!::ReadFile(m_hNamedPipe, &pData->dwSize, sizeof(PIPE_DATA), NULL, &Overlapped))
    838. {
    839. //管道已结束
    840. if (ERROR_BROKEN_PIPE == ::GetLastError())
    841. {
    842. return false;
    843. }
    844. if (ERROR_IO_PENDING != ::GetLastError())
    845. {
    846. return false;
    847. }
    848. if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
    849. {
    850. return false;
    851. }
    852. }
    853. if (pData->dwSize > PIPE_BUF_MAX_SIZE)
    854. {
    855. return false;
    856. }
    857. if (!::ReadFile(m_hNamedPipe, pData->data, pData->dwSize, NULL, &Overlapped))
    858. {
    859. if (ERROR_IO_PENDING != ::GetLastError())
    860. {
    861. return false;
    862. }
    863. if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
    864. {
    865. return false;
    866. }
    867. }
    868. if (::GetOverlappedResult(m_hNamedPipe, &Overlapped, &dwBytesTransferred, true))
    869. {
    870. isSuccess = true;
    871. if (lpBytesRead)
    872. {
    873. *lpBytesRead = dwBytesTransferred;
    874. }
    875. }
    876. if (isSuccess)
    877. {
    878. if (nSize < pData->dwSize)
    879. {
    880. ::memcpy_s(lpData, nSize, pData->data, nSize);
    881. }
    882. else
    883. {
    884. ::memcpy_s(lpData, nSize, pData->data, pData->dwSize);
    885. }
    886. }
    887. return isSuccess;
    888. }
    889. bool CNamedPipe::Write(LPCVOID lpData, DWORD nSize, LPDWORD lpBytesWritten/* = nullptr*/, DWORD nTimeOut)
    890. {
    891. OVERLAPPED Overlapped = { 0 };
    892. Overlapped.hEvent = m_hWriteEvent;
    893. DWORD dwBytesTransferred = 0;
    894. bool isSuccess = false;
    895. if (nullptr == m_pBuffer ||
    896. nullptr == lpData ||
    897. 0 == nSize ||
    898. nSize > PIPE_BUF_MAX_SIZE
    899. )
    900. {
    901. return false;
    902. }
    903. PPIPE_DATA pData = reinterpret_cast(m_pBuffer);
    904. DWORD dwBytesToWrite = nSize + sizeof(PIPE_DATA);
    905. pData->dwSize = nSize;
    906. ::memcpy_s(pData->data, PIPE_BUF_MAX_SIZE, lpData, nSize);
    907. if (::WriteFile(m_hNamedPipe, pData, dwBytesToWrite, NULL, &Overlapped))
    908. {
    909. return true;
    910. }
    911. //管道正在被关闭
    912. if (ERROR_NO_DATA == ::GetLastError())
    913. {
    914. return false;
    915. }
    916. //管道已结束
    917. if (ERROR_BROKEN_PIPE == ::GetLastError())
    918. {
    919. return false;
    920. }
    921. //重叠
    922. if (ERROR_IO_PENDING != ::GetLastError())
    923. {
    924. return false;
    925. }
    926. if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
    927. {
    928. return false;
    929. }
    930. if (::GetOverlappedResult(m_hNamedPipe, &Overlapped, &dwBytesTransferred, true))
    931. {
    932. isSuccess = true;
    933. if (lpBytesWritten)
    934. {
    935. *lpBytesWritten = dwBytesTransferred;
    936. }
    937. }
    938. return isSuccess;
    939. }
    940. bool CNamedPipe::Initialize()
    941. {
    942. bool isSuccess = false;
    943. if (m_bInit)
    944. {
    945. return true;
    946. }
    947. do
    948. {
    949. if (nullptr == m_pBuffer)
    950. {
    951. m_pBuffer = ::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, PIPE_BUF_MAX_SIZE + sizeof(PIPE_DATA));
    952. }
    953. if (NULL == m_hReadEvent)
    954. {
    955. m_hReadEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
    956. }
    957. if (NULL == m_hWriteEvent)
    958. {
    959. m_hWriteEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
    960. }
    961. if (NULL == m_hConnectEvent)
    962. {
    963. m_hConnectEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
    964. }
    965. //任意事件创建失败则终止
    966. if ((NULL == m_hReadEvent) || (NULL == m_hWriteEvent) || (NULL == m_hConnectEvent) )
    967. {
    968. break;
    969. }
    970. isSuccess = true;
    971. } while (false);
    972. if (!isSuccess)
    973. {
    974. Uninitialize();
    975. }
    976. m_bInit = isSuccess;
    977. return m_bInit;
    978. }
    979. void CNamedPipe::Uninitialize()
    980. {
    981. if (!m_bInit)
    982. {
    983. return;
    984. }
    985. //关闭管道
    986. this->Close();
    987. //释放读写缓冲
    988. if (nullptr != m_pBuffer)
    989. {
    990. ::HeapFree(::GetProcessHeap(), 0, m_pBuffer);
    991. m_pBuffer = nullptr;
    992. }
    993. m_bInit = false;
    994. }
    995. std::map<int64_t, _tstring> CLogHelper::_GetLogFileList(const _tstring& strDir)
    996. {
    997. std::map<int64_t, _tstring> fileList;
    998. WIN32_FIND_DATA findData = { 0 };
    999. HANDLE hFindHandle = INVALID_HANDLE_VALUE;
    1000. hFindHandle = FindFirstFile((strDir + _T("\\*.*")).c_str(), &findData);
    1001. if (INVALID_HANDLE_VALUE == hFindHandle)
    1002. {
    1003. return fileList;
    1004. }
    1005. do
    1006. {
    1007. _tstring strName = findData.cFileName;
    1008. //非目录
    1009. if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
    1010. {
    1011. //检查输入规则
    1012. int nConverted = 0;
    1013. SYSTEMTIME st = { 0 };
    1014. _tstring strPath = strDir + _T("\\") + strName;
    1015. _tstring strPrefix = Format(_T("%s_%%4hd-%%2hd-%%2hd_%%2hd-%%2hd-%%2hd-%%3hd.log"), m_strSaveName.c_str());
    1016. nConverted = _stscanf_s(findData.cFileName, strPrefix.c_str(),
    1017. &st.wYear, &st.wMonth, &st.wDay, &st.wHour,
    1018. &st.wMinute, &st.wSecond, &st.wMilliseconds);
    1019. //检查文件名规则是否符合要求
    1020. if (7 == nConverted)
    1021. {
    1022. FILETIME ftFile = { 0 };
    1023. FILETIME ftLocal = { 0 };
    1024. int64_t timestamp = 0;
    1025. ::SystemTimeToFileTime(&st, &ftLocal);
    1026. ::LocalFileTimeToFileTime(&ftLocal, &ftFile);
    1027. timestamp = ((int64_t)ftFile.dwHighDateTime << 32) | ftFile.dwLowDateTime;
    1028. timestamp = (timestamp - 116444736000000000) / 10000;
    1029. fileList.insert(std::make_pair(timestamp, strPath));
    1030. }
    1031. }
    1032. //上一级目录与当前目录跳过
    1033. if (0 == _tcscmp(findData.cFileName, _T(".")) || 0 == _tcscmp(findData.cFileName, _T("..")))
    1034. {
    1035. continue;
    1036. }
    1037. } while (::FindNextFile(hFindHandle, &findData));
    1038. ::FindClose(hFindHandle);
    1039. return fileList;
    1040. }
    1041. _tstring CLogUtils::Format(LPCTSTR pstrFormat, ...)
    1042. {
    1043. _tstring strResult;
    1044. LPTSTR lpBuf = nullptr;
    1045. DWORD dwCchCount = MAX_PATH;
    1046. if (nullptr == pstrFormat)
    1047. {
    1048. return strResult;
    1049. }
    1050. va_list args;
    1051. va_start(args, pstrFormat);
    1052. do
    1053. {
    1054. //分配缓冲
    1055. lpBuf = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwCchCount * sizeof(TCHAR));
    1056. if (nullptr == lpBuf)
    1057. {
    1058. break;
    1059. }
    1060. //成功则赋值字符串并终止循环
    1061. if (-1 != _vsntprintf_s(lpBuf, dwCchCount, _TRUNCATE, pstrFormat, args))
    1062. {
    1063. strResult = lpBuf;
    1064. break;
    1065. }
    1066. //释放缓冲, 待下次重新分配
    1067. ::HeapFree(::GetProcessHeap(), 0, lpBuf);
    1068. lpBuf = nullptr;
    1069. //缓冲字符数在合理范围内则扩大2倍
    1070. if (dwCchCount < INT32_MAX)
    1071. {
    1072. dwCchCount *= 2;
    1073. }
    1074. else
    1075. {
    1076. //超限终止处理
    1077. break;
    1078. }
    1079. } while (true);
    1080. va_end(args);
    1081. //释放缓冲
    1082. if (nullptr != lpBuf)
    1083. {
    1084. ::HeapFree(::GetProcessHeap(), 0, lpBuf);
    1085. lpBuf = nullptr;
    1086. }
    1087. return strResult;
    1088. }
    1089. _tstring CLogUtils::GetCurrentTimeString()
    1090. {
    1091. TCHAR szBuf[MAX_PATH] = { 0 };
    1092. SYSTEMTIME st = { 0 };
    1093. (void)::GetLocalTime(&st);
    1094. ::StringCchPrintf(
    1095. szBuf,
    1096. _countof(szBuf),
    1097. _T("%04d-%02d-%02d %02d:%02d:%02d.%03d"),
    1098. st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
    1099. );
    1100. return szBuf;
    1101. }
    1102. int64_t CLogUtils::GetCurrentTimestamp()
    1103. {
    1104. int64_t timeStamp = 0;
    1105. (void)::GetSystemTimeAsFileTime((FILETIME*)&timeStamp);
    1106. return (timeStamp - 116444736000000000) / 10000;
    1107. }
    1108. _tstring CLogUtils::TimestampToString(int64_t timestamp, const _tstring& strFormat)
    1109. {
    1110. TCHAR szBuf[MAX_PATH] = { 0 };
    1111. SYSTEMTIME st = { 0 };
    1112. FILETIME ftFile = { 0 };
    1113. FILETIME ftLocal = { 0 };
    1114. timestamp = timestamp * 10000 + 116444736000000000;
    1115. ftFile.dwLowDateTime = timestamp & 0xFFFFFFFF;
    1116. ftFile.dwHighDateTime = timestamp >> 32;
    1117. ::FileTimeToLocalFileTime(&ftFile, &ftLocal);
    1118. ::FileTimeToSystemTime(&ftLocal, &st);
    1119. ::StringCchPrintf(
    1120. szBuf,
    1121. _countof(szBuf),
    1122. strFormat.c_str(),
    1123. st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
    1124. );
    1125. return szBuf;
    1126. }
    1127. _tstring CLogUtils::GetCurrentModulePath()
    1128. {
    1129. TCHAR szCurPath[MAX_PATH] = { 0 };
    1130. ::GetModuleFileName(NULL, szCurPath, _countof(szCurPath));
    1131. _tstring strResult = szCurPath;
    1132. return strResult;
    1133. }
    1134. _tstring CLogUtils::GetCurrentModuleDir()
    1135. {
    1136. return GetFileDir(GetCurrentModulePath());
    1137. }
    1138. _tstring CLogUtils::GetCurrentModuleName(bool bHasExt/* = true*/)
    1139. {
    1140. return GetFileName(GetCurrentModulePath(), bHasExt);
    1141. }
    1142. _tstring CLogUtils::GetFileDir(const _tstring& strPath)
    1143. {
    1144. _tstring strResult;
    1145. size_t nIndex = strPath.find_last_of(_T('\\'));
    1146. if (nIndex != _tstring::npos)
    1147. {
    1148. strResult = strPath.substr(0, nIndex);
    1149. }
    1150. return strResult;
    1151. }
    1152. _tstring CLogUtils::GetFileName(const _tstring& strPath, bool bHasExt/* = true*/)
    1153. {
    1154. _tstring strResult = strPath;
    1155. size_t nIndex = strResult.find_last_of(_T('\\'));
    1156. if (nIndex != _tstring::npos)
    1157. {
    1158. strResult = strResult.substr(nIndex + 1);
    1159. }
    1160. if (!bHasExt)
    1161. {
    1162. nIndex = strResult.find_last_of(_T('.'));
    1163. if (nIndex != _tstring::npos)
    1164. {
    1165. return strResult.substr(0, nIndex);
    1166. }
    1167. }
    1168. return strResult;
    1169. }
    1170. bool CLogUtils::IsDirectory(const _tstring& strPath)
    1171. {
    1172. WIN32_FILE_ATTRIBUTE_DATA attr = { 0 };
    1173. if (!::GetFileAttributesEx(strPath.c_str(), GetFileExInfoStandard, &attr))
    1174. {
    1175. return false;
    1176. }
    1177. return attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
    1178. }
    1179. bool CLogUtils::CreateDir(const _tstring& strPath)
    1180. {
    1181. _tstring strDriver; //驱动器号, 如 D:
    1182. _tstring strSubPath = strPath; //路径, 如 Test\1\2\3
    1183. if (strPath.empty())
    1184. {
    1185. return false;
    1186. }
    1187. //获取盘符
    1188. do
    1189. {
    1190. size_t nFindIndex = strPath.find_first_of(':'); //检查是否有驱动器号
    1191. if (nFindIndex == _tstring::npos)
    1192. {
    1193. break;
    1194. }
    1195. strDriver = strPath.substr(0, nFindIndex + 1); //得到驱动器号, 如 D:
    1196. nFindIndex = strPath.find(_T("\\"), nFindIndex);
    1197. if (nFindIndex == _tstring::npos)
    1198. {
    1199. break;
    1200. }
    1201. strSubPath = strPath.substr(nFindIndex + 1); //得到路径, 如 Test\1\2\3
    1202. } while (false);
    1203. _tstring strDestDir;
    1204. size_t nFindBegin = 0;
    1205. size_t nFindIndex = 0;
    1206. do
    1207. {
    1208. nFindIndex = strSubPath.find(_T("\\"), nFindBegin);
    1209. if (nFindIndex != _tstring::npos)
    1210. {
    1211. strDestDir = strSubPath.substr(0, nFindIndex);
    1212. nFindBegin = nFindIndex + 1;
    1213. }
    1214. else
    1215. {
    1216. strDestDir = strSubPath;
    1217. }
    1218. if (!strDriver.empty())
    1219. {
    1220. strDestDir = strDriver + _T("\\") + strDestDir;
    1221. }
    1222. if (!::CreateDirectory(strDestDir.c_str(), NULL) && ERROR_ALREADY_EXISTS != ::GetLastError())
    1223. {
    1224. return false;
    1225. }
    1226. } while (nFindIndex != _tstring::npos);
    1227. return true;
    1228. }
    1229. bool CLogUtils::DeleteArchive(const _tstring& strPath)
    1230. {
    1231. if (strPath.empty())
    1232. {
    1233. return false;
    1234. }
    1235. return ::DeleteFile(strPath.c_str());
    1236. }
    1237. unsigned long long CLogUtils::GetFileSize(const _tstring& strPath)
    1238. {
    1239. unsigned long long ullSize = 0;
    1240. WIN32_FILE_ATTRIBUTE_DATA attr = { 0 };
    1241. if (!::GetFileAttributesEx(strPath.c_str(), GetFileExInfoStandard, &attr))
    1242. {
    1243. return 0;
    1244. }
    1245. ullSize = (unsigned long long)attr.nFileSizeHigh << 32 | attr.nFileSizeLow;
    1246. return ullSize;
    1247. }
    1248. CLogHelper& GetInstance()
    1249. {
    1250. return g_Instance;
    1251. }
    1252. }

    使用

    main.cpp

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include "CLogUtils.h"
    6. void PrintHelp();
    7. void PrintHelp_en_us();
    8. void PrintHelp_zh_cn();
    9. void CLogUtilsTest(int nTestCount, _tstring strPipe, _tstring strDir, _tstring strName, int nFileCount, int nFileSize, int nBufCount, int nTimeOut);
    10. int _tmain(int argc, LPCTSTR argv[])
    11. {
    12. ::setlocale(LC_ALL, "");
    13. _tstring strLogDir;
    14. _tstring strLogName;
    15. _tstring strPipeName;
    16. DWORD nTestCount = 100000;
    17. DWORD nFileSize = 1024 * 1024 * 4;
    18. DWORD nTempCount = 1000;
    19. DWORD nTimeout = 5000;
    20. DWORD nFileCount = 16;
    21. bool bSrv = true;
    22. if (argc <= 1)
    23. {
    24. PrintHelp();
    25. //return -1;
    26. }
    27. for (int i = 1; i < argc; i++)
    28. {
    29. _tstring strSwitchValue = argv[i];
    30. if (!(_T('-') == strSwitchValue[0] || _T('/') == strSwitchValue[0]))
    31. {
    32. continue;
    33. }
    34. _tstring strHelp = strSwitchValue.substr(1);
    35. if (strHelp == _T("help") || strHelp == _T("?"))
    36. {
    37. PrintHelp();
    38. return 0;
    39. }
    40. size_t nEqual = strSwitchValue.find(_T('='));
    41. if (_tstring::npos == nEqual)
    42. {
    43. continue;
    44. }
    45. _tstring strName = strSwitchValue.substr(1, nEqual - 1);
    46. _tstring strValue = strSwitchValue.substr(nEqual + 1);
    47. std::transform(strName.begin(), strName.end(), strName.begin(), [](TCHAR ch)->TCHAR {
    48. if (ch >= _T('A') && ch <= _T('Z')) ch &= ~0x20;
    49. return ch;
    50. });
    51. if (strName == _T("d"))
    52. {
    53. strLogDir = strValue;
    54. continue;
    55. }
    56. if (strName == _T("n"))
    57. {
    58. strLogName = strValue;
    59. continue;
    60. }
    61. if (strName == _T("a"))
    62. {
    63. _stscanf_s(strValue.c_str(), _T("%d"), &nTestCount);
    64. continue;
    65. }
    66. if (strName == _T("p"))
    67. {
    68. strPipeName = strValue;
    69. continue;
    70. }
    71. if (strName == _T("f"))
    72. {
    73. _stscanf_s(strValue.c_str(), _T("%d"), &nFileCount);
    74. continue;
    75. }
    76. if (strName == _T("s"))
    77. {
    78. _stscanf_s(strValue.c_str(), _T("%d"), &nFileSize);
    79. continue;
    80. }
    81. if (strName == _T("c"))
    82. {
    83. _stscanf_s(strValue.c_str(), _T("%d"), &nTempCount);
    84. continue;
    85. }
    86. if (strName == _T("t"))
    87. {
    88. _stscanf_s(strValue.c_str(), _T("%d"), &nTimeout);
    89. continue;
    90. }
    91. if (strName == _T("srv"))
    92. {
    93. bSrv = _tcstoul(strValue.c_str(), nullptr, 10) != 0;
    94. continue;
    95. }
    96. }
    97. if (bSrv)
    98. {
    99. CLogUtils::GetInstance().Initialize(true, _T("1"), _T(""), _T("Srv"), nFileSize, nFileCount, nTempCount, nTimeout * 1000);
    100. system("pause");
    101. }
    102. else
    103. {
    104. while (true)
    105. {
    106. CLogUtilsTest(nTestCount, strPipeName, strLogDir, strLogName, nFileSize, nFileCount, nTempCount, nTimeout * 1000);
    107. system("pause");
    108. }
    109. }
    110. return 0;
    111. }
    112. void CLogUtilsTest(int nTestCount, _tstring strPipe, _tstring strDir, _tstring strName, int nFileSize, int nFileCount, int nBufCount, int nTimeOut)
    113. {
    114. uint64_t uBegin = 0;
    115. uint64_t uEnd = 0;
    116. uint64_t uCost = 0;
    117. double lfSpeed = 0.0f;
    118. if (!strPipe.empty())
    119. {
    120. CLogUtils::GetInstance().Initialize(true, strPipe, strDir, strName, nFileSize, nFileCount, nBufCount, nTimeOut);
    121. uBegin = CLogUtils::GetCurrentTimestamp();
    122. for (int i = 0; i < nTestCount; i++)
    123. {
    124. CLogUtils::GetInstance().Info(_T("%d %s"), 1024, _T("FlameCyclone"));
    125. }
    126. CLogUtils::GetInstance().FlushBuffers();
    127. uEnd = CLogUtils::GetCurrentTimestamp();
    128. uCost = uEnd - uBegin;
    129. lfSpeed = (double)nTestCount * 1000 / (uCost);
    130. _tprintf(
    131. _T("Repeat %d tims, cost time: %lld, speed: %lf/S\r\n"),
    132. nTestCount,
    133. uCost,
    134. lfSpeed
    135. );
    136. }
    137. else
    138. {
    139. CLogUtils::GetInstance().Initialize(false, strPipe, strDir, strName, nFileSize, nFileCount, nBufCount, nTimeOut);
    140. uBegin = CLogUtils::GetCurrentTimestamp();
    141. for (int i = 0; i < nTestCount; i++)
    142. {
    143. CLogUtils::GetInstance().Info(_T("%d %s"), 1024, _T("FlameCyclone"));
    144. }
    145. CLogUtils::GetInstance().FlushBuffers();
    146. uEnd = CLogUtils::GetCurrentTimestamp();
    147. uCost = uEnd - uBegin;
    148. lfSpeed = (double)nTestCount * 1000 / (uCost);
    149. _tprintf(
    150. _T("Repeat %d tims, cost time: %lld, speed: %lf/S\r\n"),
    151. nTestCount,
    152. uCost,
    153. lfSpeed
    154. );
    155. }
    156. }
    157. void PrintHelp()
    158. {
    159. LANGID langID = ::GetThreadUILanguage();
    160. if (LANG_CHINESE == PRIMARYLANGID(langID))
    161. {
    162. PrintHelp_zh_cn();
    163. }
    164. else
    165. {
    166. PrintHelp_en_us();
    167. }
    168. }
    169. void PrintHelp_en_us()
    170. {
    171. std::wcout << _T("How to use:") << std::endl;
    172. std::wcout << _T("CLogUtils -options=value") << std::endl;
    173. std::wcout << std::endl;
    174. std::wcout << _T("-p=[number]: Enable pipe mode and specify the pipe name.") << std::endl;
    175. std::wcout << _T("-a=[number]: Number of test output logs.") << std::endl;
    176. std::wcout << _T("-d=[string]: The log file output directory.") << std::endl;
    177. std::wcout << _T("-n=[string]: The log file name prefix.") << std::endl;
    178. std::wcout << _T("-f=[number]: Number of log files.") << std::endl;
    179. std::wcout << _T("-s=[number]: Single log file size(bytes).") << std::endl;
    180. std::wcout << _T("-c=[number]: Number of log caches.") << std::endl;
    181. std::wcout << _T("-t=[number]: Automatically save logs to a file timeout value(seconds).") << std::endl;
    182. std::wcout << _T("-?/-help: Help for CLogSrv.") << std::endl;
    183. }
    184. void PrintHelp_zh_cn()
    185. {
    186. std::wcout << _T("使用方法:") << std::endl;
    187. std::wcout << _T("CLogUtils.exe -选项=值") << std::endl;
    188. std::wcout << std::endl;
    189. std::wcout << _T("-p=[字符串]: 启用管道模式并指定管道名.") << std::endl;
    190. std::wcout << _T("-a=[整数]: 测试输出日志数量.") << std::endl;
    191. std::wcout << _T("-d=[字符串]: 日志输出目录.") << std::endl;
    192. std::wcout << _T("-n=[字符串]: 日志文件名前缀.") << std::endl;
    193. std::wcout << _T("-f=[整数]: 日志文件数量.") << std::endl;
    194. std::wcout << _T("-s=[整数]: 单个日志文件大小(字节).") << std::endl;
    195. std::wcout << _T("-c=[整数]: 日志缓冲数量.") << std::endl;
    196. std::wcout << _T("-t=[整数]: 自动保存超时间隔(秒).") << std::endl;
    197. std::wcout << _T("-?/-help: 日志单元帮助.") << std::endl;
    198. }

  • 相关阅读:
    05 proxy_pass 携带有 uri 的场景下面的处理
    vue项目seo优化-预渲染prerender-spa-plugin配置
    优化算法 -AdGrad算法
    java-net-php-python-jsp网上飞机票订票管理系统计算机毕业设计程序
    儿童台灯怎么选对眼睛好?分享央视推荐的护眼灯
    Requests之不同类型的参数,发送请求接口!
    如何使用 Git 进行多人协作开发(全流程图解)
    Springboot+实验课程辅助管理系统 毕业设计-附源码191113
    vscode下ssh免密登录linux服务器
    黑马瑞吉外卖之员工账号的禁用和启用以及编辑修改
  • 原文地址:https://blog.csdn.net/Flame_Cyclone/article/details/133933740