1. 程式人生 > >分享個C++日誌記錄類以及日誌記錄程式

分享個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效率更高。



相關推薦

分享C++日誌記錄以及日誌記錄程式

前言 個人覺得開發中比較重要的一點就是“不要重複發明輪子”,現在國外、國內開源軟體遍地開花,尤其是Google,開源了一系列效能、架構特別好的程式碼,如果能夠用開源的應該儘量避免自己造輪子。那麼為什麼不用log4plus呢?在這裡我需要的是一個簡單實用、輕巧的日誌記錄程式,

Android : 封裝Http工具,以及日誌攔截器的工具包

這個沒有什麼好說的 同標題 package soexample.umeng.com.okhttp.utils; import android.os.Environment; import java.io.File; import java.io.IOException; import

C++中抽象以及虛/純虛、解構函式的區別與介紹

一、虛擬函式 在某基類中宣告為 virtual 並在一個或多個派生類中被重新定義的成員函式,用法格式為:virtual+函式返回型別+ 函式名(引數表) {函式體};實現多型性,通過指向派生類的基類指標或引用,訪問派生類中同名覆蓋成員函式。 二、純虛擬函式 純虛擬函式是一種

Mybatis logj日誌配置問題 以及日誌相關的所有問題

使用Mybatis的時候,有些時候能輸出(主要是指sql,引數,結果)日誌。有些時候就不能。 無法輸出日誌的時候,無論怎麼配置log4j,不管是properties的還是xml的,都不起作用。 有些時候,我們沒做什麼配置就能輸出日誌.... 這是一個讓無數人煩躁的問題。其實解決問題很容易

模糊C均值聚以及C實現

1. 基本介紹     同K均值類似,FCM演算法也是一種基於劃分的聚類演算法,它的思想就是使得被劃分到同一簇的物件之間相似度最大,而不同簇之間的相似度最小。     模糊C均值是普通C均值聚類演算法的改進,普通C均值對資料進行硬性劃分,一個樣本一定明確的屬於某一類,FC

分享C++封裝Libcurl程式碼(支援下載檔案、GET\POST、重定向斷點續傳等功能)

前言 前面分享過一個Windows上封裝Winhttp和WinInet API的程式碼,結果下載頁好評特別多(呵呵),謝謝大家賞臉。文章地址:開源一個C++實現的簡單HTTP協議處理庫,裡面有程式碼資源下載地址。但是,在實際開發過程中我發現WinHttp API嚴重依賴微

C#使用Process呼叫外部exe程式

在C#中,通過Process 類來進行程序操作。 Process類在System.Diagnostics包中。 【System.Diagnostics.Process.Start("calc.exe");】執行計算器 示例一 using System.Diagno

nginx不記錄指定文件日誌

nginx配置 tar 重啟 指定文件類型 restart quest 虛擬主機配置 service 修改 1、指定記錄文件日誌記錄的內容。 vim /usr/local/nginx/conf/nginx.conf如下部分: log_format dd ‘$remot

日誌記錄(明確FileStreamDictionary等用法)

查找 相等 man nbsp program 不存在 system ++ pset 一個好的程序對於日誌的處理是必不可少的。好的日誌記錄可以幫助我們減少更好的查找錯誤和系統的維護。今天整理一下自己工作中平時用來記錄日誌的類,同時也補補基礎知識。 功能:

Springboot 如何使用AOP同時織入多切面?實現使用者 操作日誌記錄功能

首先匯入AOP的pom <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-sta

c# 多執行緒使用佇列順序寫日誌 (需要再優化)

using System; using System.Collections.Generic; using System.Threading; public class LogManager { /// <summary> /// 建構函式 /// </su

Android日誌列印LogUtils,能夠定位到名,方法名以及出現錯誤的行數並儲存日誌檔案

  關注finddreams,一起分享,一起進步!http://blog.csdn.net/finddreams/article/details/4556

在ASP.NET MVC5中如何使用Apache log4net 庫來記錄日誌

在執行軟體程式的時候,跟蹤和監控日誌是一種記錄過程的好方法。 好東東和大家一起分享。 簡介: 在執行軟體程式的時候,跟蹤和監控日誌是一種記錄過程的好方法。尤其在應用程式出錯的時候,日誌是我們最需要的檔案。不管是在web,winform,windowsphone。 這

c/c++列印帶完整附加資訊(帶時間戳、pid等)的日誌adb logcat -v threadtime格式

近來在Linux下開發中用到c/c++和nodejs混合程式設計,看到adb logcat -v threadtime 打出來的Log那麼整齊規範,非常想把c/c++程式碼的log規範成這種效果一下, 需要這樣做有兩個原因: 1.有的人沒有用到adb, 或者log不輸出到控

python可以分開檔案記錄日誌

#!/usr/bin/python #coding=utf-8 import logging import sys import os class Logger: def __init__

c語言每隔1秒向檔案中寫入一行記錄,類似日誌記錄

讀寫一個檔案test.txt,每隔1秒向檔案中寫入一行記錄,直到按Ctrl-C終止。下次啟動程式時在test.txt檔案末尾追加記錄,並且序號能夠接續上次的序號。 #include <stdio

Spring MVC學習-----------異常處理以及日誌記錄

如何實現全域性的異常處理? 在spring MVC的配置檔案中: Xml程式碼   <!-- 總錯誤處理-->   <bean id="exceptionResolver

Log4j指定日誌記錄到指定的檔案中

LOG4J 配置檔案一段 log4j.rootLogger=ERROR,allerror,heartbeat log4j.additivity.com.cvicse.inforguard=false log4j.appender.allerror=org.apache.

C#Winform中儲存配置資訊App.Config的操作日誌操作

C#Winform中儲存配置資訊App.Config的類操作: 新增引用:System.Configuration; /// <summary> /// 配置資訊維護 /// </summary> public class A

C++日誌庫log4cplus:SocketAppender記錄日誌到log Server

1.問題 C++程式在後臺執行時,可通過log4cplus記錄日誌。當C++程式執行在遠端伺服器上時,我們就需要遠端登陸到該伺服器才能檢視日誌。進一步,如果該C++程式一個並行程式或者分散式程式,為了檢視程式的執行狀態,我們就需要登陸到N臺伺服器上,tail -f xx.