C++类设计:一个比较复杂的日志类 支持多线程、文件切换、信息缓存(源码)

初级代码游戏的专栏介绍与文章目录-CSDN博客

github位置:codetoys/ctfc.git src/env/myLog.h和.cpp


这个类功能细节比较多,因为是长期使用的版本,累积了各种功能。之前介绍的两个是我的测试代码用的版本,非常简单,那两个在这里:

C++类设计:设计一个日志类(源码)_初级代码游戏的博客-CSDN博客

C++类设计:一个不同版本的日志类(完整源码)_初级代码游戏的博客-CSDN博客

其实这个版本跟上面两个很不一样,这个版本是基于unix/linux的(64位),控制台程序,支持多线程(需要链接pthread),支持日志文件切换,统计各种信息的条数,能缓存信息,可用于如果一段代码正确结束就不输出、出错才输出。

目录

一、完整源码

二、详解

[2.1 关于endi、ende、endr](#2.1 关于endi、ende、endr)

[2.2 多线程支持](#2.2 多线程支持)

[2.3 缓存支持](#2.3 缓存支持)

[2.4 日志切换](#2.4 日志切换)


一、完整源码

头文件:

cpp 复制代码
	//输出信息所在位置的日志宏------g_pEnv->GetLog()返回Log,可以直接定义为全局变量
#define thelog ((g_pEnv->GetLog()).LogPos(__FILE__,__LINE__,__func__))
#define theLog (g_pEnv->GetLog())  //不带文件名行号函数名


//输出调试信息的宏,G_DEBUG G_TRANCE可以直接定义为全局变量
#define DEBUG_LOG if(G_DEBUG)thelog
#define TRANCE_LOG if(G_TRANCE)thelog

	//
	//日至

	struct LogEnd
	{
		enum { fENDE = 1, fENDW, fENDI, fENDF, fENDD };
		char end;
		LogEnd(int ,int n):end(n){}
	};
	ostream & operator<<(ostream & s, LogEnd const & end);//这个好像没啥用,但是我暂时没验证,不敢删
	LogEnd const ende(0, LogEnd::fENDE); // error
	LogEnd const endw(0, LogEnd::fENDW); // warning
	LogEnd const endi(0, LogEnd::fENDI); // information
	LogEnd const endf(0, LogEnd::fENDF); // fatal
	LogEnd const endd(0, LogEnd::fENDD); // debug

	class Log
	{
	private:
		//线程数据
		struct _ThreadSpec
		{
			long m_thread_id;
			stringstream m_buf;
			string m_strSource;
			bool m_bOutputThis;//仅控制当前一行输出
			string m__file;
			long m__line;
			string m__func;
			_ThreadSpec() :m_thread_id(0), m_bOutputThis(true), m__line(0) {}
		};
		static void _CloseThreadSpec(void * _p)
		{
			_ThreadSpec * p=(_ThreadSpec *)_p;
			delete p;
		}
		_ThreadSpec * _getThreadSpec()
		{
			_ThreadSpec * p = (_ThreadSpec *)pthread_getspecific(tsd_key);
			if (p)return p;
			else
			{
				p = new _ThreadSpec;
				if (!p)
				{
					throw "new _ThreadSpec return NULL";
				}
				m_mutex.lock();
				p->m_thread_id = m_thread_count;
				++m_thread_count;
				m_mutex.unlock();
				if (0 == pthread_setspecific(tsd_key, p))
				{
					return p;
				}
				else
				{
					throw "pthread_setspecific error";
				}
			}
		}
	private:
		long m_thread_count;//线程计数
		pthread_key_t tsd_key;//线程存储key	
		CPThreadMutex m_mutex;
		string m_appname;//用来构造m_filename,用于定时切换文件,若未设置则直接使用m_filename
		string m_appname_old;//切换日志时用来保存原来的,以便切换换回来
		string m_filename;//当前的日志文件名
		ofstream m_ofs;
		bool m_bOutput;//控制所有输出
		bool m_bCache;//当!m_bOutput时缓存输出结果
		string::size_type m_maxcache;//最大缓存的数目
		string m_cache;//缓存的输出

		void(*m_pUD)(const string& strLog);//用户定义的函数,输出的时候会执行一次,一般也不用

		//错误和警告统计
		long countw;
		long counte;
		long countall;
		//正常记录统计
		long countn;
	public:
        //设置用户定义的函数,输出的时候会执行一次,一般也不用
		void SetUserFunction(void(*pUD)(const string& strLog)) { m_pUD = pUD; }
	public:
		Log()
		{
			m_thread_count = 0;
			pthread_key_create(&tsd_key, _CloseThreadSpec);
			m_mutex.init();
			SetSource("应用");
			m_bOutput = true;
			m_bCache = false;
			m_maxcache = 0;
			m_pUD = NULL;

			countw = 0;
			counte = 0;
			countn = 0;
			countall = 0;
		}

		void GetCount(long & c_w, long & c_e, long & c_all)const { c_w = countw; c_e = counte; c_all = countall; }
		string const &GetFileName()const { return m_filename; }
		Log& LogPos(char const * file, long line,char const * func)
		{
			string tmp = file;
			string::size_type pos = tmp.find_last_of("/");
			if (pos != tmp.npos)_getThreadSpec()->m__file = tmp.substr(pos + 1);
			else _getThreadSpec()->m__file = file;

			_getThreadSpec()->m__line = line;
			_getThreadSpec()->m__func = func;
			return *this;
		}

		string const & _makesource(string const & source, string & ret);
		template <typename T>
		Log& operator<<(T const& t)
		{
			_getThreadSpec()->m_buf << (t);
			return *this;
		}
		Log& operator<<(stringstream const& ss)
		{
			_getThreadSpec()->m_buf << ss.str();
			return *this;
		}
		Log & operator<<(ostream &(*p)(ostream &))
		{
			_getThreadSpec()->m_buf <<(p);
			return *this;
		}
		Log & operator<<(LogEnd const & end)
		{
			m_mutex.lock();
			_ThreadSpec * ThreadSpec = _getThreadSpec();
			char nCh = end.end;
			bool isImportant = false;

			string strType;

			switch (nCh)
			{
			case LogEnd::fENDI:
				strType = "[信息]";
				++countn;
				break;
			case LogEnd::fENDW:
				strType = "[警告]";
				isImportant = true;
				++countw;
				break;
			case LogEnd::fENDE:
				strType = "[出错]";
				isImportant = true;
				++counte;
				break;
			case LogEnd::fENDF:
				strType = "[致命]";
				isImportant = true;
				break;
			case LogEnd::fENDD:
				strType = "[调试]";
				break;
			}
			if (isImportant)++countall;

			time_t t;
			tm const * t2;
			char buf[2048];
			time(&t);
			t2 = localtime(&t);
			sprintf(buf, "%02d-%02d %02d:%02d:%02d", t2->tm_mon + 1, t2->tm_mday, t2->tm_hour, t2->tm_min, t2->tm_sec);

			string strTime = buf;

			strTime = "[" + strTime + "]";

			//若未设置则不输出文件名和行号
			if (0 != ThreadSpec->m__file.size())
			{
				sprintf(buf, "[%-24s:%4ld(%s)][%6.2f]", ThreadSpec->m__file.c_str()
					, ThreadSpec->m__line, ThreadSpec->m__func.c_str(), (clock() / (float)CLOCKS_PER_SEC));
			}
			else buf[0] = '\0';

			string tmpSource;
			string strMsg;
			strMsg = strTime + _makesource(ThreadSpec->m_strSource, tmpSource) + strType + buf + ThreadSpec->m_buf.str();

			if (LogEnd::fENDE == nCh)
			{
				if (G_ERROR_MESSAGE().str().size() > 1024 * 1024)G_ERROR_MESSAGE().str(G_ERROR_MESSAGE().str().substr(512 * 1024));
				G_ERROR_MESSAGE() << strMsg << endl;
			}

			if (!m_bCache && m_filename.size() != 0)
			{
				string newfile = makelogfilename();
				if (m_filename != newfile)
				{
					m_ofs.close();
					_Open(newfile);
					cout << "文件切换,原文件 " << m_filename << " 新文件 " << newfile << endl;
					m_ofs << "文件切换,原文件 " << m_filename << " 新文件 " << newfile << endl;
				}
				m_ofs << strMsg.c_str() << endl;
				m_ofs.flush();
				if (m_ofs.bad())
				{
					m_ofs.close();
					_Open(newfile);
					cout << "写文件错误,关闭后重新打开文件" << endl;
					m_ofs << "写文件错误,关闭后重新打开文件" << endl;
					m_ofs << strMsg.c_str() << endl;
					m_ofs.flush();
				}
			}

			if (m_bOutput && ThreadSpec->m_bOutputThis)
			{
				cout << strMsg.c_str() << endl;
			}
			else
			{
				if (m_bCache)
				{
					if (m_cache.size() > m_maxcache)
					{
						m_cache.erase(0, m_cache.size() / 2);//超长时删去前半部分
					}
					m_cache += strMsg + "\n";
				}
			}
			ThreadSpec->m_bOutputThis = true;

			if (m_pUD) (*m_pUD)(strMsg); // 用户定义功能

			ThreadSpec->m__file = "";
			ThreadSpec->m__line = 0;
			ThreadSpec->m_buf.str("");
			
			m_mutex.unlock();
			return *this;
		}

		Log& SetSource(const string& strSource)
		{
			_getThreadSpec()->m_strSource = "[" + strSource + "]";
			return *this;
		}
		void setMaxFileSize(long)
		{
			cout << "theLog.setMaxFileSize(maxsize) 此功能已取消" << endl;
		}
		void setCache(string::size_type maxcache)
		{
			m_maxcache = maxcache;
			m_bCache = (maxcache > 0);
		}
		bool getCache()const { return m_bCache; }
		string & GetCachedLog(string & ret)//获得缓存的日志并清理缓存
		{
			ret = m_cache;
			m_cache.clear();
			return ret;
		}
		void ClearCache()//结束缓存,丢弃缓存的东西
		{
			m_cache.clear();
			setCache(0);
		}

		void setOutput(bool bEnable = true) { m_bOutput = bEnable; }
		bool getOutput() { return m_bOutput; }
		Log & setOutputThis(bool bEnable = false) { _getThreadSpec()->m_bOutputThis = bEnable; return *this; }
#ifdef _LINUXOS
		int _Open(const string& strFile, std::_Ios_Openmode nMode = ios::out | ios::app)
#else
		int _Open(const string& strFile, int nMode = ios::out | ios::app)
#endif
		{
			m_ofs.close();
			m_ofs.clear();
			m_ofs.open(strFile.c_str(), nMode);
			if (!m_ofs.good())
			{
				cout << "打开文件出错 " << strFile << " " << strerror(errno) << endl;
				return -1;
			}
			m_filename = strFile;

			return 1;
		}
		//以固定文件方式打开日志,不会根据日期切换
		int Open(char const * filename)
		{
			cout << "theLog.Open(filename) 此操作将取消按日期生成日志文件的功能,若要使用按日期分文件请取消此调用" << endl;
			m_appname_old = m_appname;
			m_appname = "";
			return _Open(filename);
		}
		int ReturnToOldFile()
		{
			return ActiveOpen(m_appname_old.c_str());
		}
		//根据当前年月日构造日志文件名,若未设置m_appname则为m_filename
		string makelogfilename()
		{
			if (0 == m_appname.size())return m_filename;
			time_t t;
			tm const * t2;
			char buf[256];
			time(&t);
			t2 = localtime(&t);
			sprintf(buf, "%s.%04d%02d%02d.log", m_appname.c_str(), t2->tm_year + 1900, t2->tm_mon + 1, t2->tm_mday);
			return buf;
		}
		//按日期打开日志,日期改变日志文件自动切换,格式为"appname.YYYYMMDD.log"
		int ActiveOpen(char const * appname)
		{
			m_appname = appname;
			return _Open(makelogfilename());
		}
		//获得当前日志位置
		long tellEndP()
		{
			m_ofs.seekp(0, ios::end);
			return m_ofs.tellp();
		}
		//返回当前日志记录数
		long getCountN()
		{
			return countn;
		}
		long getCountW()
		{
			return countw;
		}
		long getCountE()
		{
			return counte;
		}
	};
	//日志接口
#define LOG (thelog)
#define ENDI (endi)
#define ENDW (endw)
#define ENDE (ende)

源文件:

cpp 复制代码
    //这个好像没啥用,但是我暂时没验证,不敢删
	ostream & operator<<(ostream & s, LogEnd const & end)
	{
		switch (end.end)
		{
		case LogEnd::fENDI:
			s << "[信息]";
			break;
		case LogEnd::fENDW:
			s << "[警告]";
			break;
		case LogEnd::fENDE:
			s << "[出错]";
			break;
		case LogEnd::fENDF:
			s << "[致命]";
			break;
		case LogEnd::fENDD:
			s << "[调试]";
			break;
		}
		s << endl;
		return s;
	}

	string const & Log::_makesource(string const & source, string & ret)
	{
		if (m_thread_count < 2)return source;

		_ThreadSpec * ThreadSpec = _getThreadSpec();
		char buf[256];
		if (0 == ThreadSpec->m_thread_id)
		{
			sprintf(buf, "[%lu]", (unsigned long)getpid());
		}
		else
		{
			sprintf(buf, "[%lu-%2ld]", (unsigned long)getpid(), ThreadSpec->m_thread_id);
		}
		return ret = source + buf;
	}

代码里面用到了一个互斥对象,代码是这样的,简单包装而已:

cpp 复制代码
	//线程同步对象
	class CPThreadMutex
	{
	private:
		pthread_mutex_t m_mutex;//互斥对象
		pthread_cond_t m_cond;//条件变量
	public:
		void init()
		{
			pthread_mutex_init(&m_mutex, NULL);
			pthread_cond_init(&m_cond, NULL);
		}
		void destory()
		{
			pthread_cond_destroy(&m_cond);
			pthread_mutex_destroy(&m_mutex);
		}
		int lock()const { return pthread_mutex_lock((pthread_mutex_t*)&m_mutex); }
		int unlock()const { return pthread_mutex_unlock((pthread_mutex_t*)&m_mutex); }
		int wait()const { return pthread_cond_wait((pthread_cond_t*)&m_cond, (pthread_mutex_t*)&m_mutex); }
		int reltimedwait()const
		{
			timespec to;
			to.tv_sec = time(NULL) + 1;
			to.tv_nsec = 0;
			int ret;

			ret = pthread_cond_timedwait((pthread_cond_t*)&m_cond, (pthread_mutex_t*)&m_mutex, &to);
			if (0 == ret)return true;
			else if (ETIMEDOUT == ret)return false;
			else throw ret;
			return false;
		}
		int signal()const { return pthread_cond_signal((pthread_cond_t*)&m_cond); }
	};

二、详解

2.1 关于endi、ende、endr

这一套是传统留下来的,在结束输出的时候表明这是什么性质的日志。含义很简单:

endi 信息

endd 调试

ende 出错

endr 报告------由于endr和某些版本的oracle客户端冲突,再说也没有实际使用,所以就删掉了

这个东西可以根据需要扩展。

2.2 多线程支持

由于日志对象是个全局对象,为了支持多线程使用,需要在内部实现多线程支持。具体办法是将临时缓存的数据放在线程存储里面。这里用的是POSIX线程库:

cpp 复制代码
		//线程数据
		struct _ThreadSpec
		{
			long m_thread_id;
			stringstream m_buf;
			string m_strSource;
			bool m_bOutputThis;//仅控制当前一行输出
			string m__file;
			long m__line;
			string m__func;
			_ThreadSpec() :m_thread_id(0), m_bOutputThis(true), m__line(0) {}
		};
		static void _CloseThreadSpec(void * _p)
		{
			_ThreadSpec * p=(_ThreadSpec *)_p;
			delete p;
		}
		_ThreadSpec * _getThreadSpec()
		{
			_ThreadSpec * p = (_ThreadSpec *)pthread_getspecific(tsd_key);
			if (p)return p;
			else
			{
				p = new _ThreadSpec;
				if (!p)
				{
					throw "new _ThreadSpec return NULL";
				}
				m_mutex.lock();
				p->m_thread_id = m_thread_count;
				++m_thread_count;
				m_mutex.unlock();
				if (0 == pthread_setspecific(tsd_key, p))
				{
					return p;
				}
				else
				{
					throw "pthread_setspecific error";
				}
			}
		}

_getThreadSpec()用于获取线程数据,日志类的其他部分只用这个函数来操作数据。

为了保证输出文件不混乱,在输出的时候使用了互斥锁。

2.3 缓存支持

如果执行没有失败,就没有必要输出详细日志。程序在开始一段操作时打开缓存,执行结束检查状态,成功就清除缓存,失败才输出缓存的日志。

2.4 日志切换

日志文件不能越滚越大,一般我们按天切换文件,在每次输出的时候检查日期是否已经改变,如果改变了就关闭文件再重新打开一个。

日志太大搜索信息也很慢,一度我还用过这种策略:增加一个日志文件专门输出错误信息。

日志处理的功能全看自己的需要。


(这里是结束)

相关推荐
Swift社区42 分钟前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht44 分钟前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht1 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20241 小时前
Swift 数组
开发语言
南东山人1 小时前
一文说清:C和C++混合编程
c语言·c++
stm 学习ing2 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc3 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe3 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin3 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python