<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. 日誌記錄方式比較單一,不夠靈活,比如行號,文件名等,而且也不支持控制檯日誌輸出