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

来源:互联网 发布:单身恋恋软件 编辑:程序博客网 时间:2024/06/05 03:59

前言

个人觉得开发中比较重要的一点就是“不要重复发明轮子”,现在国外、国内开源软件遍地开花,尤其是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"//文件最大4Mstatic const int g_nMaxSize = 1024*1024*4;class CLog{public:static CLog*Instance(){static CLog log;return &log;}boolWriteLog(const char* lpLog);boolWriteLog(const wchar_t* lpLog);boolWriteLog(const char* lpFile, long lLine, const char* lpLog);boolWriteLog(const char* lpFile, long lLine, const wchar_t* lpLog);boolWriteJoinLog(const wchar_t* lpText, const wchar_t* lpLog);boolWriteJoinLog(const char* lpFile, long lLine, const wchar_t* lpText, const wchar_t* lpLog);protected:CLog();~CLog();boolInitLogFile();char*WcharToChar(const wchar_t* lpSource);private:wchar_tm_szLog[MAX_PATH];intm_nWriteSize;CLockm_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;HWNDg_hMainWnd = NULL;HWNDg_hServerWnd = NULL;HANDLEg_hLogTh = NULL;UINTg_dwLogTid = 0;// 此代码模块中包含的函数的前向声明:HWNDInitInstance(HINSTANCE);LRESULT CALLBACKWndProc(HWND, UINT, WPARAM, LPARAM);UINT __stdcallLogThread(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 onceclass CLock{public:CLock(void){ ::InitializeCriticalSection(&m_cs);}~CLock(void){ ::DeleteCriticalSection(&m_cs);}void Lock(){ ::EnterCriticalSection(&m_cs);}void UnLock(){ ::LeaveCriticalSection(&m_cs);}private:CRITICAL_SECTIONm_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效率更高。







0 0
原创粉丝点击