袖珍Windows日誌記錄工具

<span style="font-family: Arial, Helvetica, sans-serif;">#ifndef _CLOGGER_H__</span>
#define _CLOGGER_H__

#include <string>
#include <list>
#include <atlstr.h>
#include <Windows.h>

// 一條日誌最長長度
#define MAX_BUFFER_LENGTH 500

class CLogger
{
public:
    CLogger(void);
    ~CLogger(void);

    BOOL Init(const std::string&  strFilePath);
    BOOL Uninit(void);
    void Log(LPCSTR lpszFormat, ...);
    
private:
    static DWORD WINAPI LoggerThreadFunc(LPVOID lpVoid);
    DWORD LogFunc(void);
    BOOL OpenLogFile(void);
    void CloseLogFile(void);
    void WriteLogFile(LPCSTR lpszData);

    BOOL m_bExit;
    HANDLE m_hLoggerHandle;
    // 日誌記錄通知事件
    HANDLE m_hNotifyEvent;
    std::string m_strLogFile;
    FILE* m_pFileStm;
    CRITICAL_SECTION m_logLock;
    std::list<std::string> m_logDataList;
};

extern CLogger g_logger;

#endif // _CLOGGER_H__


#include "Logger.h"

CLogger::CLogger(void)
{
    m_bExit = FALSE;
    m_hLoggerHandle = NULL;
    m_hNotifyEvent = NULL;
    m_pFileStm = NULL;
}

CLogger::~CLogger(void)
{
    
}

BOOL CLogger::Init(const std::string& strFilePath)
{
    m_strLogFile = strFilePath;

    // 初始化關鍵段
    ::InitializeCriticalSection(&m_logLock);

    // 創建通知日誌記錄事件
    m_hNotifyEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!m_hNotifyEvent)
        return FALSE;

    // 創建日誌記錄線程
    m_hLoggerHandle = ::CreateThread(NULL, 0, LoggerThreadFunc, this, 0, NULL);
    if (!m_hLoggerHandle)
        return FALSE;

    return TRUE;
}

BOOL CLogger::Uninit(void)
{
    // 首先設置標記讓線程結束
    m_bExit = TRUE;

    if (m_hLoggerHandle)
    {
        // 設置日誌記錄事件爲激活狀態,爲了把暫時還沒有寫到日誌文件的日誌繼續寫完
        if (m_hNotifyEvent)
            ::SetEvent(m_hNotifyEvent);

        // 等待線程結束
        DWORD dwRet = WaitForSingleObject(m_hLoggerHandle, 60 * 1000);
        if (dwRet == WAIT_TIMEOUT)
        {
            ::TerminateThread(m_hLoggerHandle, 0);
        }
    }

    if (m_hNotifyEvent)
    {
        CloseHandle(m_hNotifyEvent);
        m_hNotifyEvent = NULL;
    }

    return TRUE;
}

DWORD CLogger::LoggerThreadFunc(LPVOID lpVoid)
{
    CLogger *logger = (CLogger*)lpVoid;
    if (logger)
        return logger->LogFunc();
    return 0;
}

DWORD CLogger::LogFunc(void)
{
    std::list<std::string> logDataList;
    do 
    {
        DWORD dwWaitRet = WaitForSingleObject(m_hNotifyEvent, INFINITE);
        // 暫時沒有日誌需要記錄
        if (dwWaitRet != WAIT_OBJECT_0)
            break;

        do 
        {
            ::EnterCriticalSection(&m_logLock);
            // 交換鏈表數據,此時logDataList一定爲空
            logDataList.swap(m_logDataList);
            // 設置事件爲無信號狀態
            ::ResetEvent(m_hNotifyEvent);
            ::LeaveCriticalSection(&m_logLock);

            if (logDataList.empty())
                break;

            if (OpenLogFile())
            {
                // 把所有的日誌寫到文件
                std::string strLog;
                while (!logDataList.empty())
                {
                    strLog = logDataList.front();
                    logDataList.pop_front();

                    WriteLogFile(strLog.c_str());
                }
                CloseLogFile();
            }
            else
            {
                break;
            }

        } while (TRUE);

        if (m_bExit)
            break;

    } while (TRUE);

    return 0;
}

void CLogger::Log(LPCSTR lpszFormat, ...)
{
    va_list arglist;
    char buffer[MAX_BUFFER_LENGTH + 1] = {0};

    va_start(arglist, lpszFormat);
    _vsnprintf(buffer, MAX_BUFFER_LENGTH, lpszFormat, arglist);
    va_end(arglist);

    SYSTEMTIME sys_time;
    ::GetLocalTime(&sys_time);

    // 得到當前時間
    char timeBuf[50] = {0};
    sprintf(timeBuf, "%d-%d-%d %02d:%02d:%02d %03d  ", 
        sys_time.wYear, sys_time.wMonth, sys_time.wDay, sys_time.wHour, sys_time.wMinute, sys_time.wSecond, sys_time.wMilliseconds);

    std::string strLog;
    strLog = timeBuf;
    strLog += buffer;

    ::EnterCriticalSection(&m_logLock);
    m_logDataList.push_back(strLog);
    ::LeaveCriticalSection(&m_logLock);

    // 設置通知日誌記錄事件爲激活狀態
    ::SetEvent(m_hNotifyEvent);
}

BOOL CLogger::OpenLogFile(void)
{
    if (!m_pFileStm)
    {
        m_pFileStm = fopen(m_strLogFile.c_str(), "ab");
    }

    return m_pFileStm != NULL;
}

void CLogger::CloseLogFile(void)
{
    if (m_pFileStm)
    {
        fclose(m_pFileStm);
        m_pFileStm = NULL;
    }
}

void CLogger::WriteLogFile(LPCSTR lpszData)
{
    if (m_pFileStm)
    {
        long length = strlen(lpszData);
        fwrite(lpszData, 1, length, m_pFileStm);
        fwrite("\r\n", 1, 2, m_pFileStm);
    }
}

CLogger g_logger;




分享一個簡單的日誌記錄小工具,這個工具是有一個獨立的類,比較獨立,相比市面上比較大的日誌記錄工具,比如log4plus(可參考這篇文章:http://blog.csdn.net/augusdi/article/details/8989494) ,zlog(可以參考這篇文章:http://blog.csdn.net/yangzhenzhen/article/details/8439459),這個工具比較小巧,不過功能也很有限。
大致的思路
首先會啓動一個線程來負責寫日誌,但是不是有數據了就會立刻寫,而是先在一個鏈表中把需要寫入的日誌數據緩存起來,
線程執行的時候會把緩存中的數據拿出來,加上本地時間之後寫入到文件中。
那麼如果一直沒有日誌數據需要寫,線程不就會一直運行,看起來就比較消耗系統資源了,畢竟日誌記錄一般來說是一個輔助的功能,
爲了解決這個問題,代碼中會使用一個事件來進行處理,如果緩存中沒有數據需要寫,那麼日誌線程就會等待日誌事件,而不是一直循環查詢緩存數據,
而在日誌記錄函數中,會向緩存中寫數據,並且同時會設置日誌事件爲激活狀態,這時日誌線程就會從等待函數中返回,繼續進行日誌的寫入。
另外一個需要注意的問題是要對數據進行加鎖。因爲日誌記錄函數中會訪問緩存數據(具體來說是對數據進行寫入),線程函數會對緩存數據進行訪問(也有寫的部分),
日誌記錄函數是在程序的主線程,或者其它線程中進行的,所以一定要加鎖,從而保證數據的一致性,不會被寫亂。


使用方法:

1. 初始化 g_logger.Init(...);

2. 寫入日誌 g_logger.Log(...)

3. 反初始化g_logger.Uninit(...)


這裏是使用了一個全局變量來使用的,這裏也可以使用一個單件模式來實現。或者使用三個宏來對着三個函數進行封裝,從而簡化使用。

存在的問題:

1. 只能在windows下使用

2. 對文件大小沒有做限制,一般來說夠用,如有特殊需求,可以給單個文件設置一個大小比如10M,達到超過指定大小之後再生產一個日誌文件,之後依次類推

3. 日誌記錄方式比較單一,不夠靈活,比如行號,文件名等,而且也不支持控制檯日誌輸出




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章