分享个C++日志记录类以及日志记录程序

前言

个人觉得开发中比较重要的一点就是“不要重复发明轮子”,现在国外、国内开源软件遍地开花,尤其是Google,开源了一系列性能、架构特别好的代码,如果能够用开源的应该尽量避免自己造轮子。那么为什么不用log4plus呢?在这里我需要的是一个简单实用、轻巧的日志记录程序,log4plus对我有点臃肿,所以才自己花店时间写了一个简单的日志记录类。

日志类实现

刚开始想的是为了避免大量的读写程序影响性能,可以保存一个大的缓存,每次写日志就往缓存中追加数据。缓存满了后,就把缓存数据写入文件中并清空缓存。这个优点很明显:I/O操作是很耗时的,写日志太过频繁,缺点:一旦日志程序异常退出,前面写的日志可能流失。那么,对应的另一种方法就是:每次写日志就打开文件写入数据,写完关闭文件,频繁操作I/O,对于性能没那么高要求的也还好。

然后就是多个线程间互斥的设置,在同一时间对一个文件操作的当然只能有一个文件。Windows上线程同步方法:临界区、事件、信号量,我这里用的是临界区。

日志记录程序实现

多个进程公用一个日志记录程序写日志,因此日志程序必须相当稳定,还要处理进程间同步的问题。我使用的是发送WM_COPYDATA消息,日志进程的消息循环会对所有进程的日志消息放入消息队列(先入先出),无需我们去处理互斥。消息循环接收到消息后,把日志数据拷贝到内存中,然后将这个数据发送到日志记录线程中。所有的日志都在一个单独的工作线程中写入文件,该线程也维护着自己的一个消息循环,处理窗口消息接收WM_COPYDATA后发送来的日志数据。同样的道理,消息循环已经为我们维护了先进先出的数据结构,我们不用考虑互斥加锁的问题,一个接着一个的写入文件就OK了。

日志类代码

/********************************************
*一个简单的日志记录类
*2015年7月28日
*Jelin
*mailto://jelinyao@163.com
*/
#pragma once
#include "../Lock.h"


//文件最大4M
static const int g_nMaxSize = 1024*1024*4;

class CLog
{
public:
	static CLog*	Instance()
	{
		static CLog log;
		return &log;
	}
	bool	WriteLog(const char* lpLog);
	bool	WriteLog(const wchar_t* lpLog);
	bool	WriteLog(const char* lpFile, long lLine, const char* lpLog);
	bool	WriteLog(const char* lpFile, long lLine, const wchar_t* lpLog);
	bool	WriteJoinLog(const wchar_t* lpText, const wchar_t* lpLog);
	bool	WriteJoinLog(const char* lpFile, long lLine, const wchar_t* lpText, const wchar_t* lpLog);

protected:
	CLog();
	~CLog();
	bool	InitLogFile();
	char*	WcharToChar(const wchar_t* lpSource);

private:
	wchar_t	m_szLog[MAX_PATH];
	int		m_nWriteSize;
	CLock	m_lock;
};

#define SLOG1(x)			CLog::Instance()->WriteLog(x);
#define SLOG2(x, y)			CLog::Instance()->WriteJoinLog(x, y);	
#define LOG1(x)				CLog::Instance()->WriteLog(__FILE__, __LINE__, x)
#define LOG2(x, y)			CLog::Instance()->WriteJoinLog(__FILE__, __LINE__, x, y)

#include "stdafx.h"
#include "Log.h"
#include <time.h>
#include <ShlObj.h>




static const char* g_lpEnd = "\n";

CLog::CLog()
	: m_nWriteSize(0)
{
	InitLogFile();
}

CLog::~CLog()
{

}

bool CLog::InitLogFile()
{
	wchar_t szPath[MAX_PATH]={0};
	::GetModuleFileName(NULL, szPath, MAX_PATH);
	for ( int i=wcslen(szPath)-1; i>=0; --i )
	{
		if ( szPath[i] == '\\' )
			break;
		szPath[i] = '\0';
	}
	wcscat(szPath, L"Log");
	SHCreateDirectory(NULL, szPath);
	SYSTEMTIME st;
	GetLocalTime(&st);
	swprintf(m_szLog, L"%s\\%d%02d%02d-%02d%02d%02d%03d.log", szPath, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
	m_nWriteSize = 0;
	return true;
}

bool CLog::WriteLog( const char* lpLog )
{
	if ( NULL == lpLog )
		return false;
	int nLen = strlen(lpLog);
	if ( 0 == nLen )
		return true;
	CScopeLock sl(m_lock);
	if ( m_nWriteSize>=g_nMaxSize )
		InitLogFile();
	FILE* fp = _wfopen(m_szLog, L"a+");
	if ( NULL == fp )
	{
		OutputDebugString(L"打开日志文件失败");
		return false;
	}
	SYSTEMTIME st;
	::GetLocalTime(&st);
	char szTime[30]={0};
	sprintf(szTime, "%d-%02d-%02d %02d:%02d:%02d\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
	fwrite(szTime, 1, strlen(szTime), fp);
	fwrite(lpLog, 1, nLen, fp);
	fwrite(g_lpEnd, 1, 1, fp);
	fclose(fp);
	m_nWriteSize += nLen+20;
	return true;
}

bool CLog::WriteLog( const wchar_t* lpLog )
{
	char* pBuffer = WcharToChar(lpLog);
	if ( NULL == pBuffer )
		return false;
	bool bRet = WriteLog(pBuffer);
	free(pBuffer);
	return bRet;
}

bool CLog::WriteLog( const char* lpFile, long lLine, const char* lpLog )
{
	if ( NULL == lpLog )
		return false;
	int nLen = strlen(lpLog);
	if ( 0 == nLen )
		return true;
	CScopeLock sl(m_lock);
	if ( m_nWriteSize>=g_nMaxSize )
		InitLogFile();
	FILE* fp = _wfopen(m_szLog, L"a+");
	if ( NULL == fp )
	{
		OutputDebugString(L"打开日志文件失败");
		return false;
	}
	SYSTEMTIME st;
	::GetLocalTime(&st);
	char szTime[30]={0};
	sprintf(szTime, "%d-%02d-%02d %02d:%02d:%02d\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
	fwrite(szTime, 1, strlen(szTime), fp);
	if ( NULL != lpFile )
		fwrite(lpFile, 1, strlen(lpFile), fp);
	char szLine[30];
	sprintf_s(szLine, " line=%ld:", lLine);
	fwrite(szLine, 1, strlen(szLine), fp);
	fwrite(lpLog, 1, nLen, fp);
	fwrite(g_lpEnd, 1, 1, fp);
	fclose(fp);
	m_nWriteSize += nLen+20;
	return true;
}

bool CLog::WriteLog( const char* lpFile, long lLine, const wchar_t* lpLog )
{
	char* lpBuffer = WcharToChar(lpLog);
	if ( NULL == lpBuffer )
		return false;
	bool bRet = WriteLog(lpFile, lLine, lpBuffer);
	free(lpBuffer);
	return bRet;
}

char* CLog::WcharToChar( const wchar_t* lpSource )
{
	if ( NULL == lpSource )
		return NULL;
	int nLen = wcslen(lpSource);
	if ( 0 == nLen )
		return NULL;
	int nNeedSize = WideCharToMultiByte(CP_ACP, 0, lpSource, nLen, NULL, 0, NULL, NULL);
	if ( 0 == nNeedSize )
		return NULL;
	char* pBuffer = (char*)malloc(sizeof(char)*(nNeedSize+1));
	if ( NULL == pBuffer )
		return NULL;
	WideCharToMultiByte(CP_ACP, 0, lpSource, nLen, pBuffer, nNeedSize, NULL, NULL);
	pBuffer[nNeedSize] = '\0';
	return pBuffer;
}

bool CLog::WriteJoinLog( const wchar_t* lpText, const wchar_t* lpLog )
{
	if ( NULL == lpText || NULL == lpLog )
		return false;
	int nTextLen = wcslen(lpText);
	int nLogLen = wcslen(lpLog);
	wchar_t* lpBuffer = (wchar_t*)malloc(sizeof(wchar_t)*(nTextLen+nLogLen+1));
	wcscpy(lpBuffer, lpText);
	wcscpy(lpBuffer+nTextLen, lpLog);
	lpBuffer[nTextLen+nLogLen] = '\0';
	bool bRet = WriteLog(lpBuffer);
	free(lpBuffer);
	return bRet;
}

bool CLog::WriteJoinLog( const char* lpFile, long lLine, const wchar_t* lpText, const wchar_t* lpLog )
{
	if ( NULL == lpFile || NULL == lpText || NULL == lpLog )
		return false;
	int nTextLen = wcslen(lpText);
	int nLogLen = wcslen(lpLog);
	wchar_t* lpBuffer = (wchar_t*)malloc(sizeof(wchar_t)*(nTextLen+nLogLen+1));
	wcscpy(lpBuffer, lpText);
	wcscpy(lpBuffer+nTextLen, lpLog);
	lpBuffer[nTextLen+nLogLen] = '\0';
	bool bRet = WriteLog(lpFile, lLine, lpBuffer);
	free(lpBuffer);
	return bRet;
}
注意,文件操作最好只在一个函数里处理,便于统一管理(加锁、解锁)。

日志程序代码(win32程序)

// Logger.cpp : 定义应用程序的入口点。
//

#include "stdafx.h"
#include "Logger.h"
#include <atlstr.h>


enum{
	WM_MSG_LOGGER = WM_USER + 200,
};



// 全局变量:
HINSTANCE hInst;
HWND	g_hMainWnd = NULL;
HWND	g_hServerWnd = NULL;
HANDLE	g_hLogTh = NULL;
UINT	g_dwLogTid = 0;

// 此代码模块中包含的函数的前向声明:
HWND				InitInstance(HINSTANCE);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
UINT __stdcall		LogThread(void* lpParam);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	try
	{
		UNREFERENCED_PARAMETER(hPrevInstance);
		UNREFERENCED_PARAMETER(lpCmdLine);
		if ( wcslen(lpCmdLine) == 0 )
		{
			TRACEW(L"参数为空\n");
			return 0;
		}
		wstring strCmd(lpCmdLine);
		string strJson = U2A(lpCmdLine);
		Json::Reader r;
		Json::Value vRoot;
		if ( !r.parse(strJson, vRoot) )
		{
			TRACEW(L"参数格式不正确,解析失败\n");
			return 0;
		}
		g_hServerWnd = (HWND)vRoot["wnd"].asInt();
		g_hMainWnd = InitInstance (hInstance);
		if ( NULL == g_hMainWnd )
		{
			TRACEW(L"创建消息窗口失败\n");
			return 0;
		}
		MSG msg;
		while (GetMessage(&msg, NULL, 0, 0))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
	catch(std::exception e)
	{
		TRACEA("捕获到异常信息:%s\n", e.what());
	}
	catch(...)
	{
		TRACEW(L"捕获到未知异常");
	}
	return 0;
}

HWND InitInstance(HINSTANCE hInstance)
{
	static const wchar_t kWndClass[] = L"ClientMessageWindow";
	WNDCLASSEX wc = {0};
	wc.cbSize = sizeof(wc);
	wc.lpfnWndProc = WndProc;
	wc.hInstance = hInstance;
	wc.lpszClassName = kWndClass;
	RegisterClassEx(&wc);
	return CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInstance, 0);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE:
		{
			//创建日志记录线程
			g_hLogTh = (HANDLE)_beginthreadex(NULL, 0, LogThread, NULL, 0, &g_dwLogTid);
			//通知发送方,日志记录进程的主窗口句柄
			::PostMessage(g_hServerWnd, WM_MSG_LOGGER, 0, (LPARAM)hWnd);
			//设置定时器,检测发送进程的状态
			::SetTimer(hWnd, 1, 3000, NULL);
			break;
		}
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	case WM_COPYDATA:
		{
			COPYDATASTRUCT* lpData = (COPYDATASTRUCT*)lParam;
			/*收到日志消息后,我们需要立即处理返回,因为发送方还在阻塞等待
			/*于是拷贝数据到内存中,将这个内存的地址投递到写日志的线程队列中去
			/*这里会频繁的 申请\释放内存,于是用到了tcmalloc,提高性能
			*/
			char* pLog = (char*)malloc(lpData->cbData+1);
			memcpy(pLog, lpData->lpData, lpData->cbData);
			pLog[lpData->cbData] = '\0';
			::PostThreadMessage(g_dwLogTid, WM_MSG_LOGGER, 0, (LPARAM)pLog);
			break;
		}
	case WM_TIMER:
		{
			//检测发送方窗口是否存在,不存在说明发送日志进程已经退出
			if ( !IsWindow(g_hServerWnd) )
			{
				::KillTimer(hWnd, 1);
				//先退出日志线程
				::PostThreadMessage(g_dwLogTid, WM_QUIT, 0, 0);
				WaitForSingleObject(g_hLogTh, 5*1000);
				CloseHandle(g_hLogTh);
				//关闭主窗口,退出进程
				::PostMessage(hWnd, WM_CLOSE, 0, 0);
			}
			break;
		}
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

//日志记录线程
UINT __stdcall LogThread(void* lpParam)
{
	MSG msg;
	while(GetMessage(&msg, NULL, 0, 0))
	{
		if ( msg.message == WM_MSG_LOGGER )
		{//由于消息队列替我们维护了先进先出的模型,这里写日志时无需加锁
			char* pLog = (char*)msg.lParam;
			SLOG1(pLog);
			free(pLog);
		}
	}
	return 0;
}
每次写入文件都会记录下写入大小,写入大小达到设定的最大值后,程序就会自动再创建一个日志文件。

还有一个

Lock.h
是一个简单的封装类,封装了下临界区的初始化,加锁解锁操作,使用十分方便。

#pragma once
class CLock
{
public:
	CLock(void)		{ ::InitializeCriticalSection(&m_cs);	}
	~CLock(void)	{ ::DeleteCriticalSection(&m_cs);		}
	void Lock()		{ ::EnterCriticalSection(&m_cs);		}
	void UnLock()	{ ::LeaveCriticalSection(&m_cs);		}
private:
	CRITICAL_SECTION	m_cs;
};

class CScopeLock
{
public:
	CScopeLock(CLock& lock)
		:m_lock(lock)
	{
		m_lock.Lock();
	}
	~CScopeLock()
	{
		m_lock.UnLock();
	}
private:
	CLock& m_lock;
};

如何使用

#define SLOG1(x)			CLog::Instance()->WriteLog(x);
#define SLOG2(x, y)			CLog::Instance()->WriteJoinLog(x, y);	
#define LOG1(x)				CLog::Instance()->WriteLog(__FILE__, __LINE__, x)
#define LOG2(x, y)			CLog::Instance()->WriteJoinLog(__FILE__, __LINE__, x, y)
简单的日志记录,不需要打印文件名和代码行:

SLOG1(L"日志第一行");

SLOG1("日志第二行");

SLOG2("日志第三行", "第三行补充文字") 用于简单文字拼接

需要打印文件名和代码行的:

LOG1("第四行日志")

LOG2("第五行日志", "补充说明")

支持ASCII和UNICODE,当然了,UNICODE内部需要转码,如果可以选择当然还是用ASCII效率更高。







  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值