袖珍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. 日志记录方式比较单一,不够灵活,比如行号,文件名等,而且也不支持控制台日志输出




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