c++ 线程安全的日志系统之LTools中的LLoger

在应用系统开发中,我们经常需要在程序中输出一些系统日志,调试日志,以及警告和错误等信息。以下是我总结与归纳的一个日志系统,能够方便的在你的应用开发中重复使用。

功能与特性

  • 支持多线程、自定义日志文件大小、最大保存天数

  • 四级日志分类:LOG,SYS,WAR,ERR

  • 支持输出到控制台,文本文件,以及VC调试窗口

  • 日志的输出格式:YYYY-MM-DDHH:MM:SS:MS [thread_id] [level] content

 1.使用了c++ 11里的smartpointer,thread

 2.若要使用宽字符,则在本文件中定义 #define USE_WCHAR即可,反之使用单字符集

 3.若要移植到Linux平台,则需要修改文件删除操作等方面的函数

在这个日志系统中用到一些c++11里面的东西。比如shared_ptr,thread等。我们在这个日志系统里面可以自定义生成的日志文件名前辍,后辍,最大保存天数,在内存中最大缓存的日志条数,即log_watermark_high = 1时,即产生一条日志立即flush到磁盘中,但这样做的效率是比较低,所以建立将log_watermark_high设置为100以上,即100条100条的写入磁盘中。

还有要注意的地方是,生成的日志文件保存在与调用日志系统的exe同层目录下的log文件夹中,所以要手动创建这个log目录。

相关文件源码:

LLog.h

/*-----------------------------------------------------------------------

 Author:		Litianming(ltm5180@126.com)

 Date:			2014-01-21

 Description:	日志相关类头文件
 
				1.支持多线程、自定义日志文件大小、最大保存天数
				2.四级日志分类:LOG,SYS,WAR,ERR
				3.支持输出到控制台,文本文件,以及VC调试窗口

 日志的输出格式:YYYY-MM-DD HH:MM:SS:MS [thread_id] [level] content

 注:
 1.使用了c++ 11 里的smart pointer,thread

 2.若要使用宽字符,则在本文件中定义 #define USE_WCHAR即可,反之使用单
 字符集

 3.若要移植到Linux平台,则需要修改文件删除操作等方面的函数

 示例

	 // 初始化日志模块
	 LTM::log_param param;
	 memset(¶m,0,sizeof(LTM::log_param));
	 param.log_file_size = 1024 * 1024 * 2;
	 param.log_max_store_days = 5;
	 param.log_watermark_high = 1;
	 param.bOpenDebugout_ = true;
	 strcpy_s(param.log_filename_prefix,"service");
	 strcpy_s(param.log_file_ext,"log");
	 LTM::LLogger::GetInstance()->init(¶m);

	 ...
	 // 任意处调用
	 LTM::LLogger::GetInstance()->Log(...);
	 ...

	 // 程序结束处释放
	 LTM::LLogger::ReleaseInstance()
 -----------------------------------------------------------------------*/
#pragma once
#ifndef _H_LLOG_
#define _H_LLOG_
#include <string>
#include <vector>
#include <memory>
#include <thread>
#include "../Base/base.h"
#include "../Base/lmutex.h"
#include "../Thread/LThreadPool.h"
namespace LTM {

	void DbgPrint(LCHAR * formatstr,...);

	// 日志级别
	enum log_type{
		LEVEL_LOG = 0,			   // 普通级
		LEVEL_ERROR,               	   // 错误级
		LEVEL_WARNING,			   // 警告级
		LEVEL_SYSTEM			   // 系统级
	};

	struct log_param {
		LCHAR	log_filename_prefix[255]; // 日志文件名前辍,如 xxxx_2014-01-24.log
		long	log_file_size;		// 单个日志文件最大大小,单位byte
		long	log_max_store_days;	// 日志最大存储天数,即保留最近的最大天数
		long	log_watermark_high;	// 日志队列的高水位标,达到该水位标则存储到存储体中
		LCHAR	log_file_ext[16];	// 日志扩展名
		bool    bOpenConsole_;		// 是否打开控制台日志输出,缺省否
		bool    bOpenDebugout_;		// 是否打开DEBUG调试输出(调试时用),缺省否
		bool	bAllDestStore_;		// 是否打开所有可打开的日志存储目标,缺省否
	};

	/************************************************************************/
	/*每条日志对应的一个该对象,基本的日志存储单元                          */
	/************************************************************************/
	class LLog
	{

	#define LOG_CONTEXT_LENGTH 512
	#define LOG_DATE_LENGTH	32

	public:
		LLog(int level_);
		~LLog(void);

		// 设置日志详细内容
		void setContext(const LCHAR* context_) 
		{
			bool bOverFlow = false;
			if (context_ == NULL)
				return;

			LCHAR* pContext = m_szContext;
			
#ifdef USE_WCHAR
			if (m_nLevel == LEVEL_LOG)
				wsprintfW(m_szContext,_T("%s %s"),_T("[LOG]"),context_);
			else if (m_nLevel == LEVEL_ERROR)
				wsprintfW(m_szContext,_T("%s %s"),_T("[ERR]"),context_);
			else if (m_nLevel == LEVEL_SYSTEM)
				wsprintfW(m_szContext,_T("%s %s"),_T("[SYS]"),context_);
			else
				wsprintfW(m_szContext,_T("%s %s"),_T("[WAR]"),context_);
#else
			if (m_nLevel == LEVEL_LOG)
				wsprintfA(m_szContext,"%s %s","[LOG]",context_);
			else if (m_nLevel == LEVEL_ERROR)
				wsprintfA(m_szContext,"%s %s","[ERR]",context_);
			else if (m_nLevel == LEVEL_SYSTEM)
				wsprintfA(m_szContext,"%s %s","[SYS]",context_);
			else
				wsprintfA(m_szContext,"%s %s","[WAR]",context_);
#endif
		}

		// 获取日志内容
		LCHAR* getContext() { return m_szContext;}

		// 将日志格式化成string类型
		std::string toString();

	private:
		DECL_PROP(unsigned int,nID);				// 日志ID
		DECL_PROP(unsigned int,nLevel);				// 日志级别
		DECL_PROP(unsigned int,nThreadID);			// 产生该条Log的线程ID
		LCHAR m_szContext[LOG_CONTEXT_LENGTH];		// 单条日志内容
		LCHAR m_szDateTime[LOG_DATE_LENGTH];		// 单条日志产生的时间
	};


	/************************************************************************/
	/*对所有的日志对象进行管理												*/
	/************************************************************************/
	class LLogger
	{
	public:
		~LLogger() { clear();}
		void init(log_param* param_ = NULL);
		void deleteLogs();
		int  scanfLogs();
		void clear();
		void flush();
		void log(LCHAR* strContext_,int nType = LEVEL_LOG);
		void print(LCHAR * formatstr,...);
		
		static unsigned __stdcall assistant(LPVOID lParam);

		static LLogger* GetInstance()
		{
			if (pInstance == NULL)
				pInstance = new LLogger();

			return pInstance;
		}

		static void ReleaseInstance() 
		{ 
			if (pInstance)
			{
				SetEvent(pInstance->m_hRunEvent);
				delete pInstance;
				pInstance = NULL;
			}

			OutputDebugStringA("[LTools]==> LLogger is quit!\n");
		}

	private:
		LLogger()
		/*:m_Assistant(assistant,(LPVOID)this)*/{}

	private:
		static LLogger*		  pInstance;					// 单实例指针
		LMutex				  m_mutex;						// 保护锁
		log_param			  m_param;						// 相关日志参数
		std::vector<std::shared_ptr<LLog>>	m_vcLogList;	// 日志对象指针列表
		std::vector<std::string> m_vcLogFileList;			// 日志文件列表
		static unsigned long  m_ulIncrease;					// 日志对象ID生成器
		std::shared_ptr<LThread>m_Assistant;				// 辅助事件处理线程
		HANDLE				  m_hRunEvent;					// 是否运行辅助事件处理线程
	};
} // end of namespace LTM
#endif

LLog.cpp

#include "stdafx.h"
#include "LLog.h"
#include "process.h"
#include <io.h>
#include <iostream>
namespace LTM {
	/*-----------------------------------------------------------------------
	* 功能: 得到指定文件的大小
	*
	* 参数: [szPath] 文件路径
	*			
	* 返回:    
	*
	* 备注:                    
	-----------------------------------------------------------------------*/
	LONGLONG GetFileSize(const std::string szPath)
	{
		WIN32_FIND_DATAA fileInfo; 
		HANDLE hFind; 
		LONGLONG  llFileSize; 	
		LARGE_INTEGER TotalFileSize;

		hFind = FindFirstFileA(szPath.c_str() ,&fileInfo); 

		if (hFind != INVALID_HANDLE_VALUE) 
		{
			TotalFileSize.LowPart  = fileInfo.nFileSizeLow;
			TotalFileSize.HighPart = fileInfo.nFileSizeHigh;
			llFileSize = TotalFileSize.QuadPart;
		}
		else
			llFileSize = 0 ;

		FindClose(hFind); 

		return llFileSize;
	}

	/*-----------------------------------------------------------------------
	* 功能: 判断指定路径的文件是否存在
	*
	* 参数:	 [strPath] 文件路径
	*			
	* 返回: 存在返回TRUE,反之返回FALSE   
	*
	* 备注:                    
	-----------------------------------------------------------------------*/
	bool CheckFileExist(const std::string strPath)
	{
		return (_access(strPath.c_str(),0) == 0);
	}

	/*-----------------------------------------------------------------------
	* 功能: 得到当前系统时间
	*
	* 参数:		
	*			
	* 返回:    
	*
	* 备注: 北京时区             
	-----------------------------------------------------------------------*/
	std::string GetLocalTimeString()
	{
		char szTime[255] = {0};
		SYSTEMTIME st;
		GetLocalTime(&st);
		wsprintfA(szTime,"%04d-%02d-%02d %02d:%02d:%02d",\
			st.wYear,st.wMonth,st.wDay,\
			st.wHour,st.wMinute,\
			st.wSecond);

		return szTime;
	}

	/*-----------------------------------------------------------------------
	* 功能: 得到安装路径
	*
	* 参数:		
	*			
	* 返回: 安装路径字符串 
	*
	* 备注:                    
	-----------------------------------------------------------------------*/
	std::string GetSetupPath()
	{
		char szFilePath[512] = {0};
		::GetModuleFileNameA(NULL, szFilePath, 512);
		std::string strFile = szFilePath;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
		/*int nCount = strFile.Find(_T('.'));
		strFile = strFile.Left(nCount);*/

		// 从后向前扫描,当发现\停止
		int i = 0;

		for(i = strFile.length() - 1; i >=0; i--)
		{
			if (strFile.at(i) == _T('.'))
				break;
		}

		strFile = strFile.substr(0,i);
		i = 0;

		for(i = strFile.length() - 1; i >= 0 ; i--)
		{
			if (strFile.at(i) == _T('\\'))
				break;
		}

		strFile = strFile.substr(0,i);
		return strFile;
	}
	//////////////////////////////////////////////////////////////////////////
	//unsigned long LLog::ulIncrease = 0;
	//boost::mutex	LLog::m_mutex;

	LLog::LLog(int level_)
	{
		memset(m_szContext,0,LOG_CONTEXT_LENGTH);
		memset(m_szDateTime,0,LOG_DATE_LENGTH);

		SYSTEMTIME st;
		GetLocalTime(&st);
#ifdef USE_WCHAR
		GetLocalTime(&st);
		wsprintfW(m_szDateTime,_T("%04d-%02d-%02d %02d:%02d:%02d:%04d"),\
			st.wYear,st.wMonth,st.wDay,\
			st.wHour,st.wMinute,\
			st.wSecond,\
			st.wMilliseconds);
#else
		wsprintfA(m_szDateTime,("%04d-%02d-%02d %02d:%02d:%02d:%04d"),\
			st.wYear,st.wMonth,st.wDay,\
			st.wHour,st.wMinute,\
			st.wSecond,\
			st.wMilliseconds);
#endif

		m_nThreadID = GetCurrentThreadId();
		m_nLevel = level_;
		m_nID = 0;
	}

	LLog::~LLog(void)
	{
		/*char buf[255] = {0};
		wsprintfA(buf,"~LLog : %x,ID : %d\n",this,m_nID);
		OutputDebugStringA(buf);*/
	}

	std::string LLog::toString()
	{
		char buf[512] = {0};
		wsprintfA(buf,"%s [%05d] %s",m_szDateTime,m_nThreadID,m_szContext);
		std::string str = buf;
		return str;
	}

	//////////////////////////////////////////////////////////////////////////
	LLogger* LLogger::pInstance = NULL;
	unsigned long  LLogger::m_ulIncrease = 0;

	/*-----------------------------------------------------------------------
	 * 功能: 初始化日志系统
	 *
	 * 参数: [param_] 相关日志参数	
	 *			
	 * 返回: 
	 *
	 * 备注:                    
	-----------------------------------------------------------------------*/
	void LLogger::init( log_param* param_ /*= NULL*/ )
	{
		if (param_ != NULL)
		{
			m_param.log_file_size	   = param_->log_file_size;
			m_param.log_max_store_days = param_->log_max_store_days;
			m_param.log_watermark_high	   = param_->log_watermark_high;

			m_param.bAllDestStore_	   = param_->bAllDestStore_;
			m_param.bOpenConsole_	   = param_->bOpenConsole_;
			m_param.bOpenDebugout_	   = param_->bOpenDebugout_;

#ifdef USE_WCHAR
			_wcsncpy_l(m_param.log_filename_prefix,param_->log_filename_prefix);
			_wcsncpy_l(m_param.log_file_ext,param_->log_file_ext);
#else
			strcpy_s(m_param.log_filename_prefix,param_->log_filename_prefix);
			strcpy_s(m_param.log_file_ext,param_->log_file_ext);
#endif
		}

		// 避免其自适应大小而影响效率
		m_vcLogList.reserve(m_param.log_watermark_high + 10);
		
		//m_Assistant = (HANDLE)_beginthreadex(NULL,0,assistant,PVOID(this),0,NULL);
		m_Assistant = LThreadPool::GetInstance()->GetFreeThread();

		if(m_Assistant)
		{
			m_Assistant->m_pFun = assistant;
			m_Assistant->m_pParam = (LPVOID)this;
			m_Assistant->Start();
		}

		OutputDebugStringA("[LTools]==> LLogger is initialization success!\n");
		/*try
		{
		m_Assistant.detach();
		}
		catch (std::exception* e)
		{
		OutputDebugStringA(e->what());
		}
		*/
		
	}

	/*-----------------------------------------------------------------------
	 * 功能: 删除过期的日志文件
	 *
	 * 参数:		
	 *			
	 * 返回:    
	 *
	 * 备注:                    
	-----------------------------------------------------------------------*/
	void LLogger::deleteLogs()
	{
		// 处理保存的最大天数
		int nLogMaxCount = m_param.log_max_store_days;
		if (nLogMaxCount < 0 || nLogMaxCount > 31)
			return;
		int nCurCount = scanfLogs();
	
		std::string strPath = GetSetupPath();
		strPath += "\\log\\";

		// 以今天为最后一天向前推算到最大保存天数的那天
		for(int i = 0; i < nCurCount - nLogMaxCount; i++)
		{
			std::string strLogFilepath = strPath + m_vcLogFileList.at(i);
			DeleteFileA(strLogFilepath.c_str());
		}

		// 重新扫描日志文件
		m_vcLogFileList.clear();
		scanfLogs();
	}

	/*-----------------------------------------------------------------------
	 * 功能: 扫描日志文件
	 *
	 * 参数:		
	 *			
	 * 返回:    
	 *
	 * 备注:                    
	-----------------------------------------------------------------------*/
	int LLogger::scanfLogs()
	{
		int nCount = 0;
		std::string strPath = GetSetupPath();
		strPath += "\\log\\*.*";

		WIN32_FIND_DATAA finddata;
		ZeroMemory(&finddata, sizeof(WIN32_FIND_DATAA));

		HANDLE hFind = FindFirstFileA(strPath.c_str() ,&finddata);

		if (hFind == INVALID_HANDLE_VALUE)
		{	
			::FindClose(hFind);
			return 0;
		}

		while(true)
		{			
			if (!FindNextFileA(hFind,&finddata))
				break;		

			std::string strFilename = finddata.cFileName;
			if (strFilename.find(m_param.log_file_ext) != -1 && strFilename.find(m_param.log_filename_prefix) != -1)
			{
				nCount++;
				m_vcLogFileList.push_back(strFilename);
			}
		}		
	
		::FindClose(hFind);
		return nCount;
	}

	/*-----------------------------------------------------------------------
	* 功能: 清空列表,释放队列中的CLog对象在堆中的内存
	*
	* 参数:		
	*			
	* 返回:  
	*
	* 备注:                    
	-----------------------------------------------------------------------*/
	void LLogger::clear()
	{
		// 释放堆上的数据
		m_mutex.lock();
		m_vcLogList.clear();
		m_mutex.unlock();
	}

	/*-----------------------------------------------------------------------
	* 功能: 将内存中的日志全部写入文件中
	*
	* 参数:		
	*			
	* 返回:  
	*
	* 备注:                    
	-----------------------------------------------------------------------*/
	void LLogger::flush()
	{
		int nCount = m_vcLogList.size();

		if (nCount < m_param.log_watermark_high)
			return ;
		
		std::string strFile = GetSetupPath();
		strFile += "\\log\\";
		strFile += m_param.log_filename_prefix;
		strFile += "-";
		std::string  strDate = GetLocalTimeString();
		strDate = strDate.substr(0,10);
		strFile += strDate;
		strFile += ".log";

		FILE *fp = NULL;

		// 检查当天的日志文件是否存在,不存在创建,存在则清空后再追加
		if (CheckFileExist(strFile))
		{
			// 如果当天日志文件大小超过限定的大小,则清空
			if (GetFileSize(strFile) >= m_param.log_file_size)
				fopen_s(&fp,strFile.c_str(),"w+");
			else
				fopen_s(&fp,strFile.c_str(),"a");
		}
		else
			fopen_s(&fp,strFile.c_str(),"w+");

		if (fp == NULL)
			return ;

		// 写入日志内容
		LCHAR buf[1024] = {0};
		
		for(unsigned int i = 0; i < m_vcLogList.size(); i++)
		{
			if (i < 0)
				break;

			std::shared_ptr<LLog> pLog = m_vcLogList.at(i);
			if (pLog == NULL)
				continue;

#ifdef USE_WCHAR
			wsprintfW(buf,_T("%s [%04d] %d %s\n"),\
				pLog->m_szDateTime,\
				pLog->m_nThreadID,\
				pLog->m_nLevel,\
				pLog->m_szContext);

			if (fp)
				fputs((CW2A)buf,fp);
#else
			wsprintfA(buf,("%s [%04d] %d %s\n"),\
				pLog->m_szDateTime,\
				pLog->m_nThreadID,\
				pLog->m_nLevel,\
				pLog->m_szContext);
			
			if (fp)
				fputs(buf,fp);
#endif	
		
			m_vcLogList.erase(m_vcLogList.begin() + i);
			i = i - 1;
		}
		
		if (fp)
			fclose(fp);
	}

	/*-----------------------------------------------------------------------
	* 功能: 处理整个日志系统的一些事务,如删除过期日志文件等
	*
	* 参数:		
	*			
	* 返回:  
	*
	* 备注:                    
	-----------------------------------------------------------------------*/
	unsigned __stdcall LLogger::assistant(LPVOID lParam)
	{
		Sleep(100);
		LLogger* pObj = (LLogger*)lParam;
		pObj->m_hRunEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
		while(1)
		{
			pObj->deleteLogs();

			if (WaitForSingleObject(pObj->m_hRunEvent,1) == WAIT_TIMEOUT)
				Sleep(1000);
			else
				break;
		}

		return 0;
	}


	void LLogger::log( LCHAR* strContext_,int level_ /*= LEVEL_LOG*/ )
	{
		int nCurLogCount = 0;
		m_mutex.lock();
		nCurLogCount = m_vcLogList.size();

		// 达到缓冲区高水位标,直接写入日志文件中
		if (nCurLogCount >= m_param.log_watermark_high)
			flush();		

		std::shared_ptr<LLog> log(new LLog(level_));

		log->SetnID(LLogger::m_ulIncrease++);
		log->setContext(strContext_);

		m_vcLogList.push_back(log);

		std::string strDebugOutput = log->toString();
		strDebugOutput += "\n";

		// 输出到调试窗口
		if (m_param.bOpenDebugout_ || m_param.bAllDestStore_)
		{
			OutputDebugStringA(strDebugOutput.c_str());
		}

		// 输出到console窗口
		if (m_param.bOpenConsole_ || m_param.bAllDestStore_)
		{
			std::cout << strDebugOutput;
		}
		m_mutex.unlock();
	}

	void LLogger::print(LCHAR * formatstr,...)
	{
		LCHAR buf[1024] = {0};

		m_mutex.lock();
		va_list argptr;
		va_start(argptr, formatstr);
#ifdef USE_WCHAR
		wvsprintfW(buf,formatstr,argptr);
#else
		wvsprintfA(buf,formatstr,argptr);
#endif
		m_mutex.unlock();

		log(buf);
	}

	void DbgPrint(LCHAR * formatstr,...)
	{
		LCHAR buf[2048] = {0};

		va_list argptr;
		va_start(argptr, formatstr);
#ifdef USE_WCHAR
		wvsprintfW(buf,formatstr,argptr);
		OutputDebugString(buf);
#else
		wvsprintfA(buf,formatstr,argptr);
		OutputDebugStringA(buf);
#endif
	}

} // end of LTM

日志文件效果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值