分享個C++日誌記錄類以及日誌記錄程序

前言

個人覺得開發中比較重要的一點就是“不要重複發明輪子”,現在國外、國內開源軟件遍地開花,尤其是Google,開源了一系列性能、架構特別好的代碼,如果能夠用開源的應該儘量避免自己造輪子。那麼爲什麼不用log4plus呢?在這裏我需要的是一個簡單實用、輕巧的日誌記錄程序,log4plus對我有點臃腫,所以才自己花店時間寫了一個簡單的日誌記錄類。

日誌類實現

剛開始想的是爲了避免大量的讀寫程序影響性能,可以保存一個大的緩存,每次寫日誌就往緩存中追加數據。緩存滿了後,就把緩存數據寫入文件中並清空緩存。這個優點很明顯:I/O操作是很耗時的,寫日誌太過頻繁,缺點:一旦日誌程序異常退出,前面寫的日誌可能流失。那麼,對應的另一種方法就是:每次寫日誌就打開文件寫入數據,寫完關閉文件,頻繁操作I/O,對於性能沒那麼高要求的也還好。

然後就是多個線程間互斥的設置,在同一時間對一個文件操作的當然只能有一個文件。Windows上線程同步方法:臨界區、事件、信號量,我這裏用的是臨界區。

日誌記錄程序實現

多個進程公用一個日誌記錄程序寫日誌,因此日誌程序必須相當穩定,還要處理進程間同步的問題。我使用的是發送WM_COPYDATA消息,日誌進程的消息循環會對所有進程的日誌消息放入消息隊列(先入先出),無需我們去處理互斥。消息循環接收到消息後,把日誌數據拷貝到內存中,然後將這個數據發送到日誌記錄線程中。所有的日誌都在一個單獨的工作線程中寫入文件,該線程也維護着自己的一個消息循環,處理窗口消息接收WM_COPYDATA後發送來的日誌數據。同樣的道理,消息循環已經爲我們維護了先進先出的數據結構,我們不用考慮互斥加鎖的問題,一個接着一個的寫入文件就OK了。

日誌類代碼

/********************************************
*一個簡單的日誌記錄類
*2015年7月28日
*Jelin
*mailto://[email protected]
*/
#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效率更高。







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