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