Win32日志实现, C++日志实现(Win32, C++)

本文介绍了个人实现的C++轻量化日志助手类,该类实现了日志常用功能,使用简便,可设置日志文件路径、容量、保存方式等。还给出了使用示例,并在不同配置下进行性能测试,展示了默认模式和管道模式下的日志写入速度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

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

功能

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

#pragma once

#include <string>
#include <windows.h>
#include <vector>
#include <map>
#include <mutex>
#include <tchar.h>
#include <thread>

//请在VS项目属性页 -> C/C++ -> 预处理器 中 添加如下宏指定日志根目录, 用于截取源码文件路径相对路径
//LOG_ROOT_DIR=R"($(ProjectDir))"
//效果: 
//添加宏之前: 2023-11-22 13:14:39.644  INFO [34688:22188] [D:\Gitee_FlameCyclone\c-log-utils\CLogUtils\CLogUtils\main.cpp:155] [CLogUtilsTest] 1024 FlameCyclone
//添加宏之后: 2023-11-22 12:31:33.45  INFO [20884:7996] [.\main.cpp:133] [CLogUtilsTest] 1024 FlameCyclone

#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif

#pragma warning(disable:4200)

namespace CLogUtils
{
    class CNamedPipe;

#define STRING_CONTENT_BUFFER_ENABLE    (1)                 //使用字符串作为日志缓冲
#define LOG_FILE_COUNT                  (16)                //最多日志文件历史数量
#define LOG_TIMEOUT                     (1000 * 60)         //自动保存超时时间(毫秒)
#define LOG_FILE_SIZE                   (1024 * 1024 * 16)  //单个日志文件大小阈值(字节)
#define LOG_BUF_COUNT                   (10000)             //日志缓冲大小阈值

#define LOG_INFO(format, ...)\
    GetInstance().Logging(_T(" INFO"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);

#ifdef _DEBUG
#define LOG_DEBUG(format, ...)\
    GetInstance().Logging(_T("DEBUG"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
#else
#define LOG_DEBUG(format, ...)\
    GetInstance().DoNothing();
#endif

#define LOG_WARN(format, ...)\
    GetInstance().Logging(_T(" WARN"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);

#define LOG_ERROR(format, ...)\
    GetInstance().Logging(_T("ERROR"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);

    class CLogHelper
    {

    public:

#define Info(format, ...)\
    Logging(_T(" INFO"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);

#ifdef _DEBUG
#define Debug(format, ...)\
    Logging(_T("DEBUG"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);
#else
#define Debug(format, ...)\
    DoNothing();
#endif

#define Warn(format, ...)\
    Logging(_T(" WARN"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);

#define Error(format, ...)\
    Logging(_T("ERROR"), _T(__FILE__), _T(__FUNCTION__), __LINE__, format, ##__VA_ARGS__);

    public:

        //默认构造
        CLogHelper();

        //删除拷贝构造与赋值重载
        CLogHelper(const CLogHelper&) = delete;

        //删除赋值重载
        CLogHelper& operator = (const CLogHelper&) = delete;

        //析构
        ~CLogHelper();

        //
        // @brief: 初始化
        // @param: bPipeMode    管道模式 true: 管道模式 false: 默认模式
        // @param: strPipeName  管道名
        // @param: strDir       存放目录
        // @param: strPreName   文件名前缀
        // @param: nFileSize    单个日志文件大小阈值(字节), 到达限制转储到下一个文件
        // @param: nTempCount   日志缓存条目限制(到达限制转储到文件)
        // @param: nTimeout     自动存储时间间隔(毫秒), 到达限制转储到文件
        // @param: nFileCount   日志文件数量限制, 到达限制删除最旧的日志文件
        // @ret: bool 执行结果
        bool Initialize(
            bool bPipeMode = false,
            const _tstring& strPipeName = _T(""),
            const _tstring& strDir = _T(""),
            const _tstring& strPreName = _T(""),
            DWORD nFileSize = LOG_FILE_SIZE,
            DWORD nFileCount = LOG_FILE_COUNT,
            DWORD nTempCount = LOG_BUF_COUNT,
            DWORD nTimeout = LOG_TIMEOUT
        );

        //
        // @brief: 反初始化
        // @ret: void
        void Uninitialize();

        //
        // @brief: 记录一条日志
        // @param: pstrLevel    日志等级
        // @param: pstrFile     源码文件
        // @param: pstrFunc     源码函数
        // @param: nLine        行数
        // @param: pstrFormat   格式化字符串
        // @param: ...          可变参数
        // @ret: void
        void Logging(
            LPCTSTR pstrLevel,
            LPCTSTR pstrFile,
            LPCTSTR pstrFunc,
            UINT nLine,
            LPCTSTR pstrFormat,
            ...
        );

        //
        // @brief: 清空已经存储的日志文件
        // @ret: void
        void Clear();

        //
        // @brief: 刷新日志缓冲(输出日志到文件)
        // @ret: bool 执行结果
        bool FlushBuffers();

        //
        // @brief: 啥也不干
        // @ret: void
        void DoNothing();

    private:

        //
        // @brief: 获取目录下文件路径
        // @ret: std::vector<_tstring> 日志文件列表
        std::map<int64_t, _tstring> _GetLogFileList(const _tstring& strDir);

        //
        // @brief: 调整日志文件数量
        // @param: void
        // @ret: void
        void _AdjustLogFile();

        //
        // @brief: 通过管道处理日志
        // @ret: bool 执行结果
        bool _ProcessByPipe();

        //
        // @brief: 超时保存处理
        // @ret: bool 执行结果
        bool _ProcessTimeoutSave();

        //
        // @brief: 管道方式记录日志
        // @ret: bool 执行结果
        bool _LoggingByPipe(const _tstring& strLogContent);

        //
        // @brief: 刷新日志缓冲(输出日志到文件)
        // @ret: bool 执行结果
        bool _FlushLogBuffers();

        //
        // @brief: 记录日志
        // @ret: bool 执行结果
        bool _LoggingContent(const _tstring& strLogContent);

        //
        // @brief: 初始化
        // @param: void
        // @ret: bool 执行结果
        bool _Initialize();

        //
        // @brief: 取消初始化
        // @param: void
        // @ret: void
        void _Uninitialize();

        //
        // @brief: 初始化日志文件
        // @param: void
        // @ret: int 日志文件索引
        void _InitLogFile();

        //
        // @brief: 生成日志转储文件路径
        // @param: void
        // @ret: void
        void _GenerateLogFilePath();

        //
        // @brief: 获取默认日志管道名
        // @param: void
        // @ret: _tstring 管道名
        _tstring _GetDefaultPipeName() const;

    private:

        std::vector<_tstring> m_logList;            //日志记录缓冲
        std::map<int64_t, _tstring> m_logFileList;  //日志文件记录, 按照时间戳排序
        std::thread m_tAutoSaveTask;                //超时自动保存任务线程
        std::thread m_tPipeRecvTask;                //管道接收任务线程
        std::mutex  m_Lock;                         //线程安全锁
        CNamedPipe* m_pRecvPipe = nullptr;          //日志接收管道
        CNamedPipe* m_pSendPipe = nullptr;          //日志发送管道

        HANDLE m_hEvent = nullptr;                  //通知事件, 使用自动转储的超时等待
        HANDLE m_hFile = INVALID_HANDLE_VALUE;      //文件句柄, 日志文件写入使用
        int64_t  m_nFileTimetamp = 0;               //日志文件时间戳
        _tstring m_strSaveDir;                      //日志存放目录
        _tstring m_strSaveName;                     //日志文件名
        _tstring m_strFilePath;                     //当前日志文件路径
        _tstring m_strLogContent;                   //日志内容
        _tstring m_strPipeName;                     //管道名
        bool m_bStop = false;                       //停止标记
        bool m_bFirst = false;                      //首次记录日志标记
        bool m_bPipeMode = false;                   //管道模式

        DWORD m_nFileSize = 0;                      //文件大小限制(到达限制则转储到文件)
        DWORD m_nTempCount = 0;                     //缓存条目限制(到达限制则转储到文件)
        DWORD m_nFileCount = 0;                     //历史文件数量限制(超限则删除旧文件)
        DWORD m_nTimeout = 0;                       //自动保存超时限制(超时则转储到文件)
        DWORD m_nCurFileSize = 0;                   //日志文件统计
        DWORD m_nNextItemSize = 0;                  //下一条日志大小
        DWORD m_nLogItemCount = 0;                  //日志缓冲统计
    };

    class CNamedPipe
    {
    public:
        CNamedPipe();

        ~CNamedPipe();

        CNamedPipe(const CNamedPipe& r) = delete;
        CNamedPipe& operator = (const CNamedPipe& r) = delete;

        //
        // @brief: 创建命名管道
        // @param: lpName           管道名
        // @ret: bool               true: 创建成功 false: 创建失败
        bool Create(LPCTSTR lpName);

        //
        // @brief: 等待客户端连接命名管道
        // @param: nTimeOut         超时等待(毫秒)
        // @ret: bool               true: 连接成功 false: 连接失败
        bool WaitConnect(DWORD nTimeOut = INFINITE);

        //
        // @brief: 关闭由Create 创建的管道
        // @param: void
        // @ret: bool               true: 关闭 成功 false: 关闭 失败
        bool Disconnect();

        //
        // @brief: 打开已存在的命名管道
        // @param: lpName           管道名
        // @ret: bool               true: 打开成功 false: 打开失败
        bool Open(LPCTSTR lpName, DWORD nTimeOut = INFINITE);

        //
        // @brief: 管道是否有效
        // @param: void
        // @ret: bool               true: 可用 false: 无效
        bool IsValid();

        //
        // @brief: 关闭管道
        // @param: void
        // @ret: void
        void Close(void);

        //
        // @brief: 从读取管道数据
        // @param: lpData           数据存放缓冲
        // @param: nSize            缓冲大小(字节)
        // @param: lpBytesRead      指向实际读取大小(字节)的指针
        // @param: nTimeOut         读取超时(毫秒)
        // @ret: bool               true: 读取成功 false: 读取失败
        bool Read(LPVOID lpData, DWORD nSize, LPDWORD lpBytesRead = nullptr, DWORD nTimeOut = INFINITE);

        //
        // @brief: 向管道写入数据
        // @param: lpData           写入数据指针
        // @param: nSize            写入数据大小(字节)
        // @param: lpBytesWritten   指向实际写入大小(字节)的指针
        // @param: nTimeOut         写入超时(毫秒)
        // @ret: bool               true: 写入成功 false: 写入失败
        bool Write(LPCVOID lpData, DWORD nSize, LPDWORD lpBytesWritten = nullptr, DWORD nTimeOut = INFINITE);

    private:

        //
        // @brief: 初始化对象占用
        // @param: void
        // @ret: void
        bool Initialize();

        //
        // @brief: 释放对象占用
        // @param: void
        // @ret: void
        void Uninitialize();

    private:
        HANDLE m_hNamedPipe = INVALID_HANDLE_VALUE;
        HANDLE m_hReadEvent = NULL;
        HANDLE m_hWriteEvent = NULL;
        HANDLE m_hConnectEvent = NULL;
        LPVOID m_pBuffer = nullptr;
        bool m_bInit = false;
        bool m_bConnected = false;
    };

    //
    // @brief: 格式化字符串
    // @param: void
    // @ret: bool 执行结果
    _tstring Format(LPCTSTR pstrFormat, ...);

    // 
    // @brief: 获取当前进程完全路径
    // @ret: 当前进程完全路径 如 D:\Software\HxDPortableSetup.exe
    _tstring GetCurrentModulePath();

    // 
    // @brief: 获取当前进程所在目录
    // @ret: 当前进程所在目录 如 D:\Software
    _tstring GetCurrentModuleDir();

    // 
    // @brief: 获取当前进程名
    // @ret: 当前进程名 如 HxDPortableSetup.exe
    _tstring GetCurrentModuleName(bool bHasExt = false);

    // 
    // @brief: 获取文件所在文件夹
    // @param: strPath     文件名, 如: D:\Software\HxDPortableSetup.exe
    // @ret: 文件夹        如 D:\Software
    _tstring GetFileDir(const _tstring& strPath);

    // 
    // @brief: 获取文件名
    // @param: strPath     文件名, 如: D:\Software\HxDPortableSetup.exe
    // @param: bHasExt     是否包含扩展名
    // @ret: 文件夹        如 HxDPortableSetup
    _tstring GetFileName(const _tstring& strPath, bool bHasExt = false);

    // 
    // @brief: 检查文件是否存在
    // @param: strPath     文件名, 如: D:\Software\HxDPortableSetup.exe
    // @ret: 是否存在      存在返回 true
    bool IsDirectory(const _tstring& strPath);

    //
    // @brief: 创建目录(递归)
    // @param: strPath      路径
    // @ret: 成功返回true
    bool CreateDir(const _tstring& strPath);

    //
    // @brief: 删除文件
    // @param: strPath      路径
    // @ret: 成功返回true
    bool DeleteArchive(const _tstring& strPath);

    //
    // @brief: 获取当前时间戳字符串
    // @param: void
    // @ret: _tstring 时间戳字符串          如: 2023-10-11 17:43:00.617
    _tstring GetCurrentTimeString();

    //
    // @brief: 获取当前时间戳
    // @param: void
    // @ret: 时间戳(单位: 毫秒)    如: 1697017380617
    int64_t GetCurrentTimestamp();

    //
    // @brief: 时间戳转字符串  
    // @param: strFormat    格式化字符串 如: "%04d-%02d-%02d %02d:%02d:%02d.03%d"
    // @param: timestamp    时间戳 如: 1697017380617
    // @ret: 时间字符串            如: 2023-10-11 17:43:00.617
    _tstring TimestampToString(
        int64_t timestamp = 0,
        const _tstring& strFormat = _T("%04d-%02d-%02d %02d:%02d:%02d.03%d")
    );

    //
    // @brief: 获取文件大小
    // @param: strPath      路径
    // @ret: 文件大小
    unsigned long long GetFileSize(const _tstring& strPath);

    CLogHelper& GetInstance();
}

CLogUtils.cpp

#include "CLogUtils.h"
#include <strsafe.h>
#include <tchar.h>
#include <shlwapi.h>
#include <future>

#pragma comment(lib, "Shlwapi.lib")

namespace CLogUtils
{
#define DEFAULT_LOG_FILE_COUNT_MIN                  (4)                     //默认最少日志文件历史数量
#define DEFAULT_LOG_TIMEOUT_MIN                     (1000 * 5)              //默认自动保存超时时间(毫秒)
#define DEFAULT_LOG_FILE_SIZE_MIN                   (1024 * 1024 * 1)       //默认单个日志文件大小阈值(字节)
#define DEFAULT_LOG_BUF_COUNT_MIN                   (256)                   //默认日志缓冲数量阈值

#define DEFAULT_LOG_FILE_COUNT_MAX                  (256)                   //默认最多日志文件历史数量
#define DEFAULT_LOG_TIMEOUT_MAX                     (1000 * 60)             //默认自动保存超时时间(毫秒)
#define DEFAULT_LOG_FILE_SIZE_MAX                   (1024 * 1024 * 256)     //默认单个日志文件大小阈值(字节)
#define DEFAULT_LOG_BUF_COUNT_MAX                   (1024 * 4)              //默认日志缓冲数量阈值

#define PIPE_NAME_PREFIX                            TEXT(R"(\\.\pipe\)")    //管道前缀名
#define PIPE_MAX_TIMEOUT                            (3000)                  //管道打开超时
#define PIPE_BUF_MAX_SIZE                           (1024 * 1024)           //管道发送缓冲大小(字节)
#define PIPE_MAX_CONNECT                            (1)                     //管道最大实例数量

    typedef struct _PIPE_DATA
    {
        DWORD dwSize = 0;
        BYTE data[0];
    }PIPE_DATA, * PPIPE_DATA;

    //全局实例构造
    static CLogHelper g_Instance;

    CLogHelper::CLogHelper() :
        m_bPipeMode(false),
        m_strPipeName(_T("")),
        m_strSaveDir(_T("")),
        m_strSaveName(_T("")),
        m_nFileSize(LOG_FILE_SIZE),
        m_nTempCount(LOG_BUF_COUNT),
        m_nTimeout(LOG_TIMEOUT),
        m_nFileCount(LOG_FILE_COUNT)
    {
    }

    CLogHelper::~CLogHelper()
    {
        this->Uninitialize();
    }

    void CLogHelper::DoNothing()
    {
    }

    bool CLogHelper::Initialize(
        bool bPipeMode/* = false*/,
        const _tstring& strPipeName/* = _T("")*/,
        const _tstring& strDir/* = _T("")*/,
        const _tstring& strPreName/* = _T("")*/,
        DWORD nFileSize/* = LOG_FILE_SIZE*/,
        DWORD nFileCount/* = LOG_FILE_COUNT*/,
        DWORD nTmpCount/* = LOG_BUF_COUNT*/,
        DWORD nTimeout/* = LOG_TIMEOUT*/
    )
    {
        m_bPipeMode = bPipeMode;
        m_strPipeName = strPipeName;
        m_strSaveDir = strDir;
        m_strSaveName = strPreName;
        m_nFileSize = nFileSize;
        m_nTempCount = nTmpCount;
        m_nTimeout = nTimeout;
        m_nFileCount = nFileCount;

        //最小参数限制
        if (m_nFileSize < DEFAULT_LOG_FILE_SIZE_MIN)
        {
            m_nFileSize = DEFAULT_LOG_FILE_SIZE_MIN;
        }

        if (m_nFileCount < DEFAULT_LOG_FILE_COUNT_MIN)
        {
            m_nFileCount = DEFAULT_LOG_FILE_COUNT_MIN;
        }

        if (m_nTempCount < DEFAULT_LOG_BUF_COUNT_MIN)
        {
            m_nTempCount = DEFAULT_LOG_BUF_COUNT_MIN;
        }

        if (m_nTimeout < DEFAULT_LOG_TIMEOUT_MIN)
        {
            m_nTimeout = DEFAULT_LOG_TIMEOUT_MIN;
        }

        //最大参数限制
        if (m_nFileSize > DEFAULT_LOG_FILE_SIZE_MAX)
        {
            m_nFileSize = DEFAULT_LOG_FILE_SIZE_MAX;
        }

        if (m_nFileCount > DEFAULT_LOG_FILE_COUNT_MAX)
        {
            m_nFileCount = DEFAULT_LOG_FILE_COUNT_MAX;
        }

        if (m_nTempCount > DEFAULT_LOG_BUF_COUNT_MAX)
        {
            m_nTempCount = DEFAULT_LOG_BUF_COUNT_MAX;
        }

        if (m_nTimeout > DEFAULT_LOG_TIMEOUT_MAX)
        {
            m_nTimeout = DEFAULT_LOG_TIMEOUT_MAX;
        }

        //默认目录为当前进程目录
        if (m_strSaveDir.empty())
        {
            m_strSaveDir = GetCurrentModuleDir();
        }

        //默认文件名为当前进程名
        if (m_strSaveName.empty())
        {
            m_strSaveName = GetCurrentModuleName(true);
        }

        //目录不存在就创建目录
        if (!IsDirectory(m_strSaveDir))
        {
            CreateDir(m_strSaveDir);
        }

        if (m_strPipeName.empty())
        {
            m_strPipeName = _GetDefaultPipeName();
        }

        return this->_Initialize();
    }

    void CLogHelper::Uninitialize()
    {
        _Uninitialize();
    }

    bool CLogHelper::_Initialize()
    {
        _Uninitialize();

        // 管道模式
        if (m_bPipeMode)
        {
            m_pRecvPipe = new (std::nothrow) CNamedPipe;
            m_pSendPipe = new (std::nothrow) CNamedPipe;

            if (nullptr == m_pRecvPipe)
            {
                return false;
            }

            if (nullptr == m_pSendPipe)
            {
                return false;
            }

            std::promise<bool> m;
            std::future<bool> p = m.get_future();

            m_tPipeRecvTask = std::move(
                std::thread([this, &m]() -> void
                    {
                        m.set_value(true);
                        _ProcessByPipe();
                    }
                )
            );

            //等待线程启动完毕
            p.get();
        }

        // 超时自动保存任务
        {
            if (NULL == m_hEvent)
            {
                m_hEvent = ::CreateEvent(nullptr, false, false, nullptr);
            }

            if (NULL == m_hEvent)
            {
                return false;
            }

            std::promise<bool> m;
            std::future<bool> p = m.get_future();

            m_tAutoSaveTask = std::move(
                std::thread([this, &m]()
                    {
                        m.set_value(true);
                        _ProcessTimeoutSave();
                    }
                )
            );

            //等待线程启动完毕
            p.get();
        }

        return true;
    }

    void CLogHelper::_Uninitialize()
    {
        if (!m_logList.empty() || !m_strLogContent.empty())
        {
            FlushBuffers();
        }

        if (INVALID_HANDLE_VALUE != m_hFile)
        {
            ::CloseHandle(m_hFile);
            m_hFile = INVALID_HANDLE_VALUE;
        }

        if (NULL != m_hEvent)
        {
            m_bStop = true;
            ::SetEvent(m_hEvent);
        }

        if (m_tAutoSaveTask.joinable())
        {
            m_tAutoSaveTask.join();
        }

        if (NULL != m_hEvent)
        {
            ::CloseHandle(m_hEvent);
            m_hEvent = NULL;
        }

        if (m_bPipeMode)
        {
            if (m_pRecvPipe)
            {
                m_pRecvPipe->Close();
            }

            if (m_pSendPipe)
            {
                m_pSendPipe->Close();
            }

            if (m_tPipeRecvTask.joinable())
            {
                m_tPipeRecvTask.join();
            }
        }

        if (nullptr != m_pRecvPipe)
        {
            delete m_pRecvPipe;
            m_pRecvPipe = nullptr;
        }

        if (nullptr != m_pSendPipe)
        {
            delete m_pSendPipe;
            m_pSendPipe = nullptr;
        }

        m_bFirst = false;
        m_bStop = false;
    }

    bool CLogHelper::_LoggingByPipe(const _tstring& strLogContent)
    {
        bool bSuccess = false;

        if (nullptr == m_pSendPipe)
        {
            return false;
        }

        if (!m_pSendPipe->IsValid())
        {
            if (!m_pSendPipe->Open(m_strPipeName.c_str(), 1000))
            {
                return false;
            }
        }

        //写入日志内容到管道, 交给另一端处理(可用跨进程)
        bSuccess = m_pSendPipe->Write(strLogContent.c_str(), (DWORD)((strLogContent.size() + 1) * sizeof(TCHAR)));
        if (!bSuccess)
        {
            m_pSendPipe->Close();
        }

        return bSuccess;
    }

    bool CLogHelper::_FlushLogBuffers()
    {
        DWORD dwNumberOfBytesWrite = 0;
        bool bSuccess = false;

        if (INVALID_HANDLE_VALUE == m_hFile)
        {
            return false;
        }

#if STRING_CONTENT_BUFFER_ENABLE
        //没有需要写入的日志
        if (m_strLogContent.empty())
        {
            return true;
        }

        bSuccess = ::WriteFile(m_hFile, m_strLogContent.c_str(), (DWORD)(m_strLogContent.size() * sizeof(TCHAR)), &dwNumberOfBytesWrite, NULL);
        m_strLogContent.clear();
#else
        //没有需要写入的日志
        if (m_logList.empty())
        {
            return true;
        }

        for (const auto& item : m_logList)
        {
            bSuccess = ::WriteFile(m_hFile, item.c_str(), (DWORD)(item.size() * sizeof(TCHAR)), &dwNumberOfBytesWrite, NULL);
            if (!bSuccess)
            {
                break;
            }
        }

#endif

        return bSuccess;
    }

    bool CLogHelper::_LoggingContent(const _tstring& strLogContent)
    {
        //获取单行日志内容 + 固定前缀内容 + 真实内容
        m_nNextItemSize = (DWORD)(strLogContent.size() * sizeof(TCHAR));

        std::lock_guard<std::mutex> lock(m_Lock);

        //首次启动时, 重置大小统计
        if (!m_bFirst)
        {
            _InitLogFile();
            _AdjustLogFile();
            m_nCurFileSize = (DWORD)GetFileSize(m_strFilePath);
            m_bFirst = true;
        }

        //单个日志文件大小即将达到或超过阈值则输出到文件, 启用新的文件存储
        if ((m_nCurFileSize + m_nNextItemSize) >= m_nFileSize)
        {
            _FlushLogBuffers();
            m_logList.clear();

            ::CloseHandle(m_hFile);
            m_hFile = INVALID_HANDLE_VALUE;

            (void)_GenerateLogFilePath();
            m_nCurFileSize = (DWORD)GetFileSize(m_strFilePath);
            _AdjustLogFile();
        }

        //已缓存条目达到阈值则输出到文件
#if STRING_CONTENT_BUFFER_ENABLE
        else if (m_nLogItemCount >= m_nTempCount)
        {
            _FlushLogBuffers();
            m_strLogContent.clear();
            m_nLogItemCount = 0;
        }
#else
        else if (m_logList.size() >= m_nTempCount)
        {
            _FlushLogBuffers();
            m_logList.clear();
        }

#endif

#if STRING_CONTENT_BUFFER_ENABLE
        m_strLogContent += strLogContent;
        m_nLogItemCount++;
#else
        m_logList.emplace_back(strLogContent);
#endif

        //累加统计单个日志文件大小
        m_nCurFileSize += m_nNextItemSize;
        return true;
    }

    bool CLogHelper::_ProcessTimeoutSave()
    {
        while (!m_bStop)
        {
            DWORD dwWait = ::WaitForSingleObject(m_hEvent, m_nTimeout);
            switch (dwWait)
            {
            case WAIT_TIMEOUT:
            case WAIT_OBJECT_0:
            {
                std::lock_guard<std::mutex> lock(m_Lock);
                this->_FlushLogBuffers();
                m_logList.clear();
            }
            break;
            default:
                break;
            }
        }

        return true;
    }

    bool CLogHelper::_ProcessByPipe()
    {
        if (m_pRecvPipe->IsValid())
        {
            return true;
        }

        if (!m_pRecvPipe->Create(m_strPipeName.c_str()))
        {
            return false;
        }

        DWORD dwPipeBufSize = PIPE_BUF_MAX_SIZE;
        LPTSTR lpData = nullptr;
        bool bFailed = false;

        lpData = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwPipeBufSize);
        if (nullptr == lpData)
        {
            return false;
        }

        while (!m_bStop && !bFailed)
        {
            if (!m_pRecvPipe->WaitConnect())
            {
                _tprintf(_T("WaitConnect failed!\r\n"));
                break;
            }

            while (!m_bStop)
            {
                bool isSuccess = m_pRecvPipe->Read(lpData, dwPipeBufSize);
                if (isSuccess)
                {
                    _LoggingContent(lpData);
                }

                if (ERROR_BROKEN_PIPE == ::GetLastError())
                {
                    m_pRecvPipe->Disconnect();
                    if (!m_pRecvPipe->WaitConnect())
                    {
                        _tprintf(_T("WaitConnect failed!\r\n"));
                        bFailed = true;
                        break;
                    }
                }

                if (!m_pRecvPipe->IsValid())
                {
                    break;
                }
            }
        }

        this->_FlushLogBuffers();
        m_logList.clear();

        if (nullptr != lpData)
        {
            ::HeapFree(::GetProcessHeap(), 0, lpData);
            lpData = nullptr;
        }

        return true;
    }

    void CLogHelper::Logging(
        LPCTSTR pstrLevel,
        LPCTSTR pstrFile,
        LPCTSTR pstrFunc,
        UINT nLine,
        LPCTSTR pstrFormat,
        ...
    )
    {
        if (nullptr == pstrFormat)
        {
            return;
        }

        TCHAR szBuf[MAX_PATH] = { 0 };
        DWORD dwPid = ::GetCurrentProcessId();
        DWORD dwTid = ::GetCurrentThreadId();
        _tstring strLogContent;
        SYSTEMTIME st = { 0 };
        (void)::GetLocalTime(&st);

#ifdef LOG_ROOT_DIR

        //日志格式前缀 [时间] [等级] [十进制进程ID:十进制线程ID] [源码位置:行数] [函数名]
        //相对路径显示源码文件路径

        _TCHAR szRelativePath[MAX_PATH] = { 0 };
        if (::PathRelativePathTo(szRelativePath, _T(LOG_ROOT_DIR), FILE_ATTRIBUTE_DIRECTORY, pstrFile, 0))
        {
            ::StringCchPrintf(
                szBuf,
                _countof(szBuf),
                _T("%04d-%02d-%02d %02d:%02d:%02d.%03d %s [%d:%d] [%s:%d] [%s] "),
                st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
                pstrLevel,
                dwPid,
                dwTid,
                szRelativePath,
                nLine,
                pstrFunc
            );
        }
        else
        {
            ::StringCchPrintf(
                szBuf,
                _countof(szBuf),
                _T("%04d-%02d-%02d %02d:%02d:%02d.%03d %s [%d:%d] [%s:%d] [%s] "),
                st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
                pstrLevel,
                dwPid,
                dwTid,
                pstrFile,
                nLine,
                pstrFunc
            );
        }

#else
        //日志格式前缀 [时间] [等级] [十进制进程ID:十进制线程ID] [源码位置:行数] [函数名]
        ::StringCchPrintf(
            szBuf,
            _countof(szBuf),
            _T("%04d-%02d-%02d %02d:%02d:%02d.%03d %s [%d:%d] [%s:%d] [%s] "),
            st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
            pstrLevel,
            dwPid,
            dwTid,
            pstrFile,
            nLine,
            pstrFunc
        );

#endif

        strLogContent = szBuf;

        va_list args;
        va_start(args, pstrFormat);

        LPTSTR pFormatBuf = nullptr;              //格式化日志缓冲
        DWORD dwFormatBufCch = MAX_PATH;          //格式化日志缓冲大小

        do
        {
            //分配缓冲
            pFormatBuf = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwFormatBufCch * sizeof(TCHAR));
            if (nullptr == pFormatBuf)
            {
                break;
            }

            //成功则赋值字符串并终止循环
            if (-1 != _vsntprintf_s(pFormatBuf, dwFormatBufCch, _TRUNCATE, pstrFormat, args))
            {
                strLogContent += pFormatBuf;
                break;
            }

            //释放缓冲, 待下次重新分配
            ::HeapFree(::GetProcessHeap(), 0, pFormatBuf);
            pFormatBuf = nullptr;

            //超限终止处理
            if (dwFormatBufCch >= INT32_MAX)
            {
                break;
            }

            dwFormatBufCch *= 2;

        } while (true);
        va_end(args);

        //释放缓冲
        if (nullptr != pFormatBuf)
        {
            ::HeapFree(::GetProcessHeap(), 0, pFormatBuf);
            pFormatBuf = nullptr;
        }

        strLogContent += _T("\r\n");

        if (m_bPipeMode)
        {
            _LoggingByPipe(strLogContent);
        }
        else
        {
            _LoggingContent(strLogContent);
        }
    }

    void CLogHelper::Clear()
    {
        std::lock_guard<std::mutex> lock(m_Lock);

        if (INVALID_HANDLE_VALUE != m_hFile)
        {
            ::CloseHandle(m_hFile);
            m_hFile = INVALID_HANDLE_VALUE;
        }

        m_logFileList = _GetLogFileList(m_strSaveDir);
        for (const auto& item : m_logFileList)
        {
            DeleteArchive(item.second);
        }

        m_logFileList.clear();
    }

    bool CLogHelper::FlushBuffers()
    {
        std::lock_guard<std::mutex> lock(m_Lock);
        return _FlushLogBuffers();
    }

    void CLogHelper::_AdjustLogFile()
    {
        //检查文件数量是否到达阈值, 到达的话删除前面的文件
        if (m_logFileList.size() > m_nFileCount)
        {
            size_t nDeleteCount = m_logFileList.size() - m_nFileCount;

            //从日志文件记录列表中删除
            for (size_t i = 0; i < nDeleteCount; i++)
            {
                auto itBegin = m_logFileList.begin();
                DeleteArchive(itBegin->second);
                m_logFileList.erase(m_logFileList.begin());
            }
        }
    }

    void CLogHelper::_InitLogFile()
    {
        //如果上次最后一个日志文件大小还能存储日志, 就沿用上次的日志文件
        m_logFileList = _GetLogFileList(m_strSaveDir);
        if (!m_logFileList.empty())
        {
            auto itLast = m_logFileList.end();
            itLast--;

            m_nFileTimetamp = itLast->first;
            m_strFilePath = itLast->second;

            //上次最后一个日志文件不能存储更多日志, 则生成新的日志文件路径
            unsigned long long ullFileSize = (DWORD)GetFileSize(m_strFilePath);
            if ((ullFileSize + m_nNextItemSize) >= m_nFileSize)
            {
                (void)_GenerateLogFilePath();
            }
            else
            {
                //打开文件以续写日志
                m_hFile = CreateFile(
                    m_strFilePath.c_str(),
                    GENERIC_WRITE,
                    FILE_SHARE_READ,
                    NULL,
                    OPEN_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL
                );

                //在文件末尾追加内容
                LARGE_INTEGER  liDistanceToMove = { 0 };
                ::SetFilePointerEx(m_hFile, liDistanceToMove, NULL, FILE_END);
            }
        }
        else
        {
            (void)_GenerateLogFilePath();
        }
    }

    //
    // @brief: 获取日志管道名
    // @param: void
    // @ret: _tstring 管道名
    _tstring CLogHelper::_GetDefaultPipeName() const
    {
        return Format(_T("%s_%s"), m_strSaveDir.c_str(), m_strSaveName.c_str());
    }

    void CLogHelper::_GenerateLogFilePath()
    {
        //得到日志文件时间戳
        m_nFileTimetamp = GetCurrentTimestamp();

        //得到日志文件路径
        m_strFilePath = Format(_T("%s\\%s_%s.log"),
            m_strSaveDir.c_str(),
            m_strSaveName.c_str(),
            TimestampToString(m_nFileTimetamp, _T("%04d-%02d-%02d_%02d-%02d-%02d-%03d")).c_str()
        );

        //创建一下文件(防止在资源管理器中看不到新的日志文件)
        m_hFile = CreateFile(
            m_strFilePath.c_str(),
            GENERIC_WRITE,
            FILE_SHARE_READ,
            NULL,
            OPEN_ALWAYS,
            FILE_ATTRIBUTE_NORMAL,
            NULL
        );

        //在文件末尾追加内容
        LARGE_INTEGER  liDistanceToMove = { 0 };
        ::SetFilePointerEx(m_hFile, liDistanceToMove, NULL, FILE_END);

        m_logFileList.insert(std::make_pair(m_nFileTimetamp, m_strFilePath));
    }

    CNamedPipe::CNamedPipe() :
        m_pBuffer(nullptr),
        m_hNamedPipe(INVALID_HANDLE_VALUE),
        m_hReadEvent(NULL),
        m_hWriteEvent(NULL),
        m_bConnected(false),
        m_bInit(false)
    {
        //初始化读写缓冲与事件句柄
        Initialize();
    }

    CNamedPipe::~CNamedPipe()
    {
        //释放读写缓冲与事件句柄
        Uninitialize();
    }

    bool CNamedPipe::Create(LPCTSTR lpName)
    {
        TCHAR szPipeName[MAX_PATH];
        SECURITY_ATTRIBUTES sa = { 0 };
        SECURITY_DESCRIPTOR sd = { 0 };
        bool isSuccess = false;

        sa.nLength = sizeof(sa);
        sa.bInheritHandle = FALSE;
        sa.lpSecurityDescriptor = &sd;

        if (INVALID_HANDLE_VALUE != m_hNamedPipe)
        {
            return true;
        }

        //设置权限, 防止低权限进程不能打开高权限进程创建的管道
        (void)::InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
        (void)::SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
        (void)::StringCchPrintf(szPipeName, _countof(szPipeName), TEXT("%s%s"), PIPE_NAME_PREFIX, lpName);

        do
        {
            m_hNamedPipe = ::CreateNamedPipe(
                szPipeName,
                PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
                PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
                PIPE_MAX_CONNECT,
                PIPE_BUF_MAX_SIZE,
                PIPE_BUF_MAX_SIZE,
                PIPE_MAX_TIMEOUT,
                &sa
            );

            if (INVALID_HANDLE_VALUE == m_hNamedPipe)
            {
                break;
            }

            isSuccess = true;

        } while (false);

        if (!isSuccess)
        {
            this->Close();
        }

        return isSuccess;
    }

    bool CNamedPipe::Open(LPCTSTR lpName, DWORD nTimeOut/* = INFINITE*/)
    {
        TCHAR szPipeName[MAX_PATH] = { 0 };
        bool isSuccess = false;

        (void)::StringCchPrintf(szPipeName, _countof(szPipeName), TEXT("%s%s"), PIPE_NAME_PREFIX, lpName);

        if (INVALID_HANDLE_VALUE != m_hNamedPipe)
        {
            return true;
        }

        ULONGLONG ullCurTick = ::GetTickCount64();

        do
        {
            m_hNamedPipe = ::CreateFile(
                szPipeName,
                GENERIC_READ | GENERIC_WRITE,
                0,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                NULL
            );

            //管道句柄有效则终止循环
            if (INVALID_HANDLE_VALUE != m_hNamedPipe)
            {
                isSuccess = true;
                break;
            }

            //若错误原因不是因为所有管道范例都在使用中, 则退出循环
            if (ERROR_PIPE_BUSY != ::GetLastError())
            {
                break;
            }

            //等待命名管道的实例可用于连接
            if (::WaitNamedPipe(szPipeName, 1000))
            {
                continue;
            }

            //无限等待则不需要检查超时
            if (INFINITE == nTimeOut)
            {
                continue;
            }

            //执行操作超时则退出循环
            if (::GetTickCount64() - ullCurTick > nTimeOut)
            {
                break;
            }

        } while (INVALID_HANDLE_VALUE == m_hNamedPipe);

        if (!isSuccess)
        {
            this->Close();
        }

        return isSuccess;
    }

    bool CNamedPipe::WaitConnect(DWORD nTimeOut)
    {
        OVERLAPPED Overlapped = { 0 };
        bool isConnected = false;

        if (INVALID_HANDLE_VALUE == m_hNamedPipe)
        {
            return false;
        }

        Overlapped.hEvent = m_hConnectEvent;
        isConnected = ::ConnectNamedPipe(m_hNamedPipe, &Overlapped);
        if (!isConnected)
        {
            DWORD dwError = ::GetLastError();

            //管道关闭中
            if (ERROR_NO_DATA == dwError)
            {
                isConnected = false;
            }
            else if (ERROR_IO_PENDING == dwError)//操作处于挂起状态
            {
                if (WAIT_OBJECT_0 == ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
                {
                    isConnected = true;
                }
            }
            else if (ERROR_PIPE_CONNECTED == dwError)//管道已经连接
            {
                isConnected = true;
            }
        }

        m_bConnected = isConnected;

        return isConnected;
    }

    bool CNamedPipe::Disconnect()
    {
        if (INVALID_HANDLE_VALUE == m_hNamedPipe)
        {
            return false;
        }

        //参数句柄必须由 CreateNamedPipe 函数创建
        return ::DisconnectNamedPipe(m_hNamedPipe);
    }

    void CNamedPipe::Close()
    {
        if (INVALID_HANDLE_VALUE != m_hNamedPipe)
        {
            if (m_bConnected)
            {
                ::FlushFileBuffers(m_hNamedPipe);
                ::DisconnectNamedPipe(m_hNamedPipe);
                m_bConnected = false;
            }
            ::CloseHandle(m_hNamedPipe);
            m_hNamedPipe = INVALID_HANDLE_VALUE;
        }

        if (m_hReadEvent)
        {
            ::SetEvent(m_hReadEvent);
            ::CloseHandle(m_hReadEvent);
            m_hReadEvent = NULL;
        }

        if (m_hWriteEvent)
        {
            ::SetEvent(m_hWriteEvent);
            ::CloseHandle(m_hWriteEvent);
            m_hWriteEvent = NULL;
        }

        if (m_hConnectEvent)
        {
            ::SetEvent(m_hConnectEvent);
            ::CloseHandle(m_hConnectEvent);
            m_hConnectEvent = NULL;
        }
    }

    bool CNamedPipe::IsValid()
    {
        return INVALID_HANDLE_VALUE != m_hNamedPipe;
    }

    bool CNamedPipe::Read(LPVOID lpData, DWORD nSize, LPDWORD lpBytesRead/* = nullptr*/, DWORD nTimeOut)
    {
        OVERLAPPED Overlapped = { 0 };
        Overlapped.hEvent = m_hReadEvent;
        DWORD dwBytesTransferred = 0;
        bool isSuccess = false;

        if (nullptr == m_pBuffer ||
            nullptr == lpData ||
            0 == nSize ||
            nSize > PIPE_BUF_MAX_SIZE
            )
        {
            return false;
        }

        PPIPE_DATA pData = reinterpret_cast<PPIPE_DATA>(m_pBuffer);
        if (!::ReadFile(m_hNamedPipe, &pData->dwSize, sizeof(PIPE_DATA), NULL, &Overlapped))
        {
            //管道已结束
            if (ERROR_BROKEN_PIPE == ::GetLastError())
            {
                return false;
            }

            if (ERROR_IO_PENDING != ::GetLastError())
            {
                return false;
            }

            if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
            {
                return false;
            }
        }

        if (pData->dwSize > PIPE_BUF_MAX_SIZE)
        {
            return false;
        }

        if (!::ReadFile(m_hNamedPipe, pData->data, pData->dwSize, NULL, &Overlapped))
        {
            if (ERROR_IO_PENDING != ::GetLastError())
            {
                return false;
            }

            if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
            {
                return false;
            }
        }

        if (::GetOverlappedResult(m_hNamedPipe, &Overlapped, &dwBytesTransferred, true))
        {
            isSuccess = true;
            if (lpBytesRead)
            {
                *lpBytesRead = dwBytesTransferred;
            }
        }

        if (isSuccess)
        {
            if (nSize < pData->dwSize)
            {
                ::memcpy_s(lpData, nSize, pData->data, nSize);
            }
            else
            {
                ::memcpy_s(lpData, nSize, pData->data, pData->dwSize);
            }
        }

        return isSuccess;
    }

    bool CNamedPipe::Write(LPCVOID lpData, DWORD nSize, LPDWORD lpBytesWritten/* = nullptr*/, DWORD nTimeOut)
    {
        OVERLAPPED Overlapped = { 0 };
        Overlapped.hEvent = m_hWriteEvent;
        DWORD dwBytesTransferred = 0;
        bool isSuccess = false;

        if (nullptr == m_pBuffer ||
            nullptr == lpData ||
            0 == nSize ||
            nSize > PIPE_BUF_MAX_SIZE
            )
        {
            return false;
        }

        PPIPE_DATA pData = reinterpret_cast<PPIPE_DATA>(m_pBuffer);
        DWORD dwBytesToWrite = nSize + sizeof(PIPE_DATA);
        pData->dwSize = nSize;
        ::memcpy_s(pData->data, PIPE_BUF_MAX_SIZE, lpData, nSize);

        if (::WriteFile(m_hNamedPipe, pData, dwBytesToWrite, NULL, &Overlapped))
        {
            return true;
        }

        //管道正在被关闭
        if (ERROR_NO_DATA == ::GetLastError())
        {
            return false;
        }

        //管道已结束
        if (ERROR_BROKEN_PIPE == ::GetLastError())
        {
            return false;
        }

        //重叠
        if (ERROR_IO_PENDING != ::GetLastError())
        {
            return false;
        }

        if (WAIT_OBJECT_0 != ::WaitForSingleObject(Overlapped.hEvent, nTimeOut))
        {
            return false;
        }

        if (::GetOverlappedResult(m_hNamedPipe, &Overlapped, &dwBytesTransferred, true))
        {
            isSuccess = true;
            if (lpBytesWritten)
            {
                *lpBytesWritten = dwBytesTransferred;
            }
        }

        return isSuccess;
    }

    bool CNamedPipe::Initialize()
    {
        bool isSuccess = false;

        if (m_bInit)
        {
            return true;
        }

        do
        {
            if (nullptr == m_pBuffer)
            {
                m_pBuffer = ::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, PIPE_BUF_MAX_SIZE + sizeof(PIPE_DATA));
            }

            if (NULL == m_hReadEvent)
            {
                m_hReadEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
            }

            if (NULL == m_hWriteEvent)
            {
                m_hWriteEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
            }

            if (NULL == m_hConnectEvent)
            {
                m_hConnectEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
            }

            //任意事件创建失败则终止
            if ((NULL == m_hReadEvent) || (NULL == m_hWriteEvent) || (NULL == m_hConnectEvent) )
            {
                break;
            }

            isSuccess = true;

        } while (false);

        if (!isSuccess)
        {
            Uninitialize();
        }

        m_bInit = isSuccess;

        return m_bInit;
    }

    void CNamedPipe::Uninitialize()
    {
        if (!m_bInit)
        {
            return;
        }

        //关闭管道
        this->Close();

        //释放读写缓冲
        if (nullptr != m_pBuffer)
        {
            ::HeapFree(::GetProcessHeap(), 0, m_pBuffer);
            m_pBuffer = nullptr;
        }

        m_bInit = false;
    }

    std::map<int64_t, _tstring> CLogHelper::_GetLogFileList(const _tstring& strDir)
    {
        std::map<int64_t, _tstring> fileList;

        WIN32_FIND_DATA findData = { 0 };
        HANDLE hFindHandle = INVALID_HANDLE_VALUE;
        hFindHandle = FindFirstFile((strDir + _T("\\*.*")).c_str(), &findData);
        if (INVALID_HANDLE_VALUE == hFindHandle)
        {
            return fileList;
        }

        do
        {
            _tstring strName = findData.cFileName;

            //非目录
            if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
            {
                //检查输入规则
                int nConverted = 0;
                SYSTEMTIME st = { 0 };

                _tstring strPath = strDir + _T("\\") + strName;
                _tstring strPrefix = Format(_T("%s_%%4hd-%%2hd-%%2hd_%%2hd-%%2hd-%%2hd-%%3hd.log"), m_strSaveName.c_str());
                nConverted = _stscanf_s(findData.cFileName, strPrefix.c_str(),
                    &st.wYear, &st.wMonth, &st.wDay, &st.wHour,
                    &st.wMinute, &st.wSecond, &st.wMilliseconds);

                //检查文件名规则是否符合要求
                if (7 == nConverted)
                {
                    FILETIME ftFile = { 0 };
                    FILETIME ftLocal = { 0 };
                    int64_t timestamp = 0;

                    ::SystemTimeToFileTime(&st, &ftLocal);
                    ::LocalFileTimeToFileTime(&ftLocal, &ftFile);

                    timestamp = ((int64_t)ftFile.dwHighDateTime << 32) | ftFile.dwLowDateTime;
                    timestamp = (timestamp - 116444736000000000) / 10000;

                    fileList.insert(std::make_pair(timestamp, strPath));
                }
            }

            //上一级目录与当前目录跳过
            if (0 == _tcscmp(findData.cFileName, _T(".")) || 0 == _tcscmp(findData.cFileName, _T("..")))
            {
                continue;
            }

        } while (::FindNextFile(hFindHandle, &findData));

        ::FindClose(hFindHandle);

        return fileList;
    }

    _tstring CLogUtils::Format(LPCTSTR pstrFormat, ...)
    {
        _tstring strResult;
        LPTSTR lpBuf = nullptr;
        DWORD dwCchCount = MAX_PATH;

        if (nullptr == pstrFormat)
        {
            return strResult;
        }

        va_list args;
        va_start(args, pstrFormat);
        do
        {
            //分配缓冲
            lpBuf = (LPTSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, dwCchCount * sizeof(TCHAR));
            if (nullptr == lpBuf)
            {
                break;
            }

            //成功则赋值字符串并终止循环
            if (-1 != _vsntprintf_s(lpBuf, dwCchCount, _TRUNCATE, pstrFormat, args))
            {
                strResult = lpBuf;
                break;
            }

            //释放缓冲, 待下次重新分配
            ::HeapFree(::GetProcessHeap(), 0, lpBuf);
            lpBuf = nullptr;

            //缓冲字符数在合理范围内则扩大2倍
            if (dwCchCount < INT32_MAX)
            {
                dwCchCount *= 2;
            }
            else
            {
                //超限终止处理
                break;
            }

        } while (true);
        va_end(args);

        //释放缓冲
        if (nullptr != lpBuf)
        {
            ::HeapFree(::GetProcessHeap(), 0, lpBuf);
            lpBuf = nullptr;
        }

        return strResult;
    }

    _tstring CLogUtils::GetCurrentTimeString()
    {
        TCHAR szBuf[MAX_PATH] = { 0 };
        SYSTEMTIME st = { 0 };
        (void)::GetLocalTime(&st);

        ::StringCchPrintf(
            szBuf,
            _countof(szBuf),
            _T("%04d-%02d-%02d %02d:%02d:%02d.%03d"),
            st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
        );

        return szBuf;
    }

    int64_t CLogUtils::GetCurrentTimestamp()
    {
        int64_t timeStamp = 0;
        (void)::GetSystemTimeAsFileTime((FILETIME*)&timeStamp);
        return (timeStamp - 116444736000000000) / 10000;
    }

    _tstring CLogUtils::TimestampToString(int64_t timestamp, const _tstring& strFormat)
    {
        TCHAR szBuf[MAX_PATH] = { 0 };
        SYSTEMTIME st = { 0 };
        FILETIME ftFile = { 0 };
        FILETIME ftLocal = { 0 };

        timestamp = timestamp * 10000 + 116444736000000000;
        ftFile.dwLowDateTime = timestamp & 0xFFFFFFFF;
        ftFile.dwHighDateTime = timestamp >> 32;

        ::FileTimeToLocalFileTime(&ftFile, &ftLocal);
        ::FileTimeToSystemTime(&ftLocal, &st);

        ::StringCchPrintf(
            szBuf,
            _countof(szBuf),
            strFormat.c_str(),
            st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
        );

        return szBuf;
    }

    _tstring CLogUtils::GetCurrentModulePath()
    {
        TCHAR szCurPath[MAX_PATH] = { 0 };
        ::GetModuleFileName(NULL, szCurPath, _countof(szCurPath));

        _tstring strResult = szCurPath;
        return strResult;
    }

    _tstring CLogUtils::GetCurrentModuleDir()
    {
        return GetFileDir(GetCurrentModulePath());
    }

    _tstring CLogUtils::GetCurrentModuleName(bool bHasExt/* = true*/)
    {
        return GetFileName(GetCurrentModulePath(), bHasExt);
    }

    _tstring CLogUtils::GetFileDir(const _tstring& strPath)
    {
        _tstring strResult;
        size_t nIndex = strPath.find_last_of(_T('\\'));
        if (nIndex != _tstring::npos)
        {
            strResult = strPath.substr(0, nIndex);
        }

        return strResult;
    }

    _tstring CLogUtils::GetFileName(const _tstring& strPath, bool bHasExt/* = true*/)
    {
        _tstring strResult = strPath;
        size_t nIndex = strResult.find_last_of(_T('\\'));
        if (nIndex != _tstring::npos)
        {
            strResult = strResult.substr(nIndex + 1);
        }

        if (!bHasExt)
        {
            nIndex = strResult.find_last_of(_T('.'));
            if (nIndex != _tstring::npos)
            {
                return strResult.substr(0, nIndex);
            }
        }

        return strResult;
    }

    bool CLogUtils::IsDirectory(const _tstring& strPath)
    {
        WIN32_FILE_ATTRIBUTE_DATA attr = { 0 };
        if (!::GetFileAttributesEx(strPath.c_str(), GetFileExInfoStandard, &attr))
        {
            return false;
        }

        return attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
    }

    bool CLogUtils::CreateDir(const _tstring& strPath)
    {
        _tstring strDriver;              //驱动器号, 如 D:
        _tstring strSubPath = strPath;   //路径, 如 Test\1\2\3

        if (strPath.empty())
        {
            return false;
        }

        //获取盘符
        do
        {
            size_t nFindIndex = strPath.find_first_of(':');  //检查是否有驱动器号
            if (nFindIndex == _tstring::npos)
            {
                break;
            }

            strDriver = strPath.substr(0, nFindIndex + 1); //得到驱动器号, 如 D:
            nFindIndex = strPath.find(_T("\\"), nFindIndex);
            if (nFindIndex == _tstring::npos)
            {
                break;
            }

            strSubPath = strPath.substr(nFindIndex + 1); //得到路径, 如 Test\1\2\3
        } while (false);

        _tstring strDestDir;
        size_t nFindBegin = 0;
        size_t nFindIndex = 0;
        do
        {
            nFindIndex = strSubPath.find(_T("\\"), nFindBegin);
            if (nFindIndex != _tstring::npos)
            {
                strDestDir = strSubPath.substr(0, nFindIndex);
                nFindBegin = nFindIndex + 1;
            }
            else
            {
                strDestDir = strSubPath;
            }

            if (!strDriver.empty())
            {
                strDestDir = strDriver + _T("\\") + strDestDir;
            }

            if (!::CreateDirectory(strDestDir.c_str(), NULL) && ERROR_ALREADY_EXISTS != ::GetLastError())
            {
                return false;
            }

        } while (nFindIndex != _tstring::npos);

        return true;
    }

    bool CLogUtils::DeleteArchive(const _tstring& strPath)
    {
        if (strPath.empty())
        {
            return false;
        }

        return ::DeleteFile(strPath.c_str());
    }

    unsigned long long CLogUtils::GetFileSize(const _tstring& strPath)
    {
        unsigned long long ullSize = 0;
        WIN32_FILE_ATTRIBUTE_DATA attr = { 0 };
        if (!::GetFileAttributesEx(strPath.c_str(), GetFileExInfoStandard, &attr))
        {
            return 0;
        }

        ullSize = (unsigned long long)attr.nFileSizeHigh << 32 | attr.nFileSizeLow;

        return ullSize;
    }

    CLogHelper& GetInstance()
    {
        return g_Instance;
    }
}

使用

main.cpp

#include <iostream>
#include <algorithm>
#include <strsafe.h>
#include <stdio.h>
#include "CLogUtils.h"

void PrintHelp();
void PrintHelp_en_us();
void PrintHelp_zh_cn();
void CLogUtilsTest(int nTestCount, _tstring strPipe, _tstring strDir, _tstring strName, int nFileCount, int nFileSize, int nBufCount, int nTimeOut);

int _tmain(int argc, LPCTSTR argv[])
{
    ::setlocale(LC_ALL, "");

    _tstring strLogDir;
    _tstring strLogName;
    _tstring strPipeName;
    DWORD nTestCount = 100000;
    DWORD nFileSize = 1024 * 1024 * 4;
    DWORD nTempCount = 1000;
    DWORD nTimeout = 5000;
    DWORD nFileCount = 16;
    bool bSrv = true;

    if (argc <= 1)
    {
        PrintHelp();
        //return -1;
    }

    for (int i = 1; i < argc; i++)
    {
        _tstring strSwitchValue = argv[i];

        if (!(_T('-') == strSwitchValue[0] || _T('/') == strSwitchValue[0]))
        {
            continue;
        }

        _tstring strHelp = strSwitchValue.substr(1);
        if (strHelp == _T("help") || strHelp == _T("?"))
        {
            PrintHelp();
            return 0;
        }

        size_t nEqual = strSwitchValue.find(_T('='));
        if (_tstring::npos == nEqual)
        {
            continue;
        }

        _tstring strName = strSwitchValue.substr(1, nEqual - 1);
        _tstring strValue = strSwitchValue.substr(nEqual + 1);

        std::transform(strName.begin(), strName.end(), strName.begin(), [](TCHAR ch)->TCHAR {
            if (ch >= _T('A') && ch <= _T('Z')) ch &= ~0x20;
            return ch;
            });

        if (strName == _T("d"))
        {
            strLogDir = strValue;
            continue;
        }

        if (strName == _T("n"))
        {
            strLogName = strValue;
            continue;
        }

        if (strName == _T("a"))
        {
            _stscanf_s(strValue.c_str(), _T("%d"), &nTestCount);
            continue;
        }

        if (strName == _T("p"))
        {
            strPipeName = strValue;
            continue;
        }

        if (strName == _T("f"))
        {
            _stscanf_s(strValue.c_str(), _T("%d"), &nFileCount);
            continue;
        }

        if (strName == _T("s"))
        {
            _stscanf_s(strValue.c_str(), _T("%d"), &nFileSize);
            continue;
        }

        if (strName == _T("c"))
        {
            _stscanf_s(strValue.c_str(), _T("%d"), &nTempCount);
            continue;
        }

        if (strName == _T("t"))
        {
            _stscanf_s(strValue.c_str(), _T("%d"), &nTimeout);
            continue;
        }

        if (strName == _T("srv"))
        {
            bSrv = _tcstoul(strValue.c_str(), nullptr, 10) != 0;
            continue;
        }
    }

    if (bSrv)
    {
        CLogUtils::GetInstance().Initialize(true, _T("1"), _T(""), _T("Srv"), nFileSize, nFileCount, nTempCount, nTimeout * 1000);
        system("pause");
    }
    else
    {
        while (true)
        {
            CLogUtilsTest(nTestCount, strPipeName, strLogDir, strLogName, nFileSize, nFileCount, nTempCount, nTimeout * 1000);
            system("pause");
        }
    }

    return 0;
}

void CLogUtilsTest(int nTestCount, _tstring strPipe, _tstring strDir, _tstring strName, int nFileSize, int nFileCount, int nBufCount, int nTimeOut)
{
    uint64_t uBegin = 0;
    uint64_t uEnd = 0;
    uint64_t uCost = 0;
    double lfSpeed = 0.0f;

    if (!strPipe.empty())
    {
        CLogUtils::GetInstance().Initialize(true, strPipe, strDir, strName, nFileSize, nFileCount, nBufCount, nTimeOut);
        uBegin = CLogUtils::GetCurrentTimestamp();

        for (int i = 0; i < nTestCount; i++)
        {
            CLogUtils::GetInstance().Info(_T("%d %s"), 1024, _T("FlameCyclone"));
        }
        CLogUtils::GetInstance().FlushBuffers();

        uEnd = CLogUtils::GetCurrentTimestamp();
        uCost = uEnd - uBegin;
        lfSpeed = (double)nTestCount * 1000 / (uCost);

        _tprintf(
            _T("Repeat %d tims, cost time: %lld, speed: %lf/S\r\n"),
            nTestCount,
            uCost,
            lfSpeed
        );
    }
    else
    {
        CLogUtils::GetInstance().Initialize(false, strPipe, strDir, strName, nFileSize, nFileCount, nBufCount, nTimeOut);
        uBegin = CLogUtils::GetCurrentTimestamp();

        for (int i = 0; i < nTestCount; i++)
        {
            CLogUtils::GetInstance().Info(_T("%d %s"), 1024, _T("FlameCyclone"));
        }
        CLogUtils::GetInstance().FlushBuffers();

        uEnd = CLogUtils::GetCurrentTimestamp();
        uCost = uEnd - uBegin;
        lfSpeed = (double)nTestCount * 1000 / (uCost);

        _tprintf(
            _T("Repeat %d tims, cost time: %lld, speed: %lf/S\r\n"),
            nTestCount,
            uCost,
            lfSpeed
        );
    }
}

void PrintHelp()
{
    LANGID langID = ::GetThreadUILanguage();

    if (LANG_CHINESE == PRIMARYLANGID(langID))
    {
        PrintHelp_zh_cn();
    }
    else
    {
        PrintHelp_en_us();
    }
}

void PrintHelp_en_us()
{
    std::wcout << _T("How to use:") << std::endl;
    std::wcout << _T("CLogUtils -options=value") << std::endl;
    std::wcout << std::endl;

    std::wcout << _T("-p=[number]:        Enable pipe mode and specify the pipe name.") << std::endl;
    std::wcout << _T("-a=[number]:        Number of test output logs.") << std::endl;
    std::wcout << _T("-d=[string]:        The log file output directory.") << std::endl;
    std::wcout << _T("-n=[string]:        The log file name prefix.") << std::endl;
    std::wcout << _T("-f=[number]:        Number of log files.") << std::endl;
    std::wcout << _T("-s=[number]:        Single log file size(bytes).") << std::endl;
    std::wcout << _T("-c=[number]:        Number of log caches.") << std::endl;
    std::wcout << _T("-t=[number]:        Automatically save logs to a file timeout value(seconds).") << std::endl;
    std::wcout << _T("-?/-help:           Help for CLogSrv.") << std::endl;
}

void PrintHelp_zh_cn()
{
    std::wcout << _T("使用方法:") << std::endl;
    std::wcout << _T("CLogUtils.exe -选项=值") << std::endl;
    std::wcout << std::endl;

    std::wcout << _T("-p=[字符串]:        启用管道模式并指定管道名.") << std::endl;
    std::wcout << _T("-a=[整数]:          测试输出日志数量.") << std::endl;
    std::wcout << _T("-d=[字符串]:        日志输出目录.") << std::endl;
    std::wcout << _T("-n=[字符串]:        日志文件名前缀.") << std::endl;
    std::wcout << _T("-f=[整数]:          日志文件数量.") << std::endl;
    std::wcout << _T("-s=[整数]:          单个日志文件大小(字节).") << std::endl;
    std::wcout << _T("-c=[整数]:          日志缓冲数量.") << std::endl;
    std::wcout << _T("-t=[整数]:          自动保存超时间隔(秒).") << std::endl;
    std::wcout << _T("-?/-help:           日志单元帮助.") << std::endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值