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

这篇具有很好参考价值的文章主要介绍了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.2 多线程支持

2.3 缓存支持

2.4 日志切换


一、完整源码

        头文件:

	//输出信息所在位置的日志宏------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)

        源文件:

    //这个好像没啥用,但是我暂时没验证,不敢删
	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;
	}

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

	//线程同步对象
	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线程库:

		//线程数据
		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 日志切换

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

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

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


(这里是结束)文章来源地址https://www.toymoban.com/news/detail-846740.html

到了这里,关于C++类设计:一个比较复杂的日志类 支持多线程、文件切换、信息缓存(源码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【教程】一个比较良心的C++代码混淆器

    这是一个比较良心的C++代码混淆器,用于信息竞赛训练和保护代码免受抄袭。本文将介绍这个混淆器的使用方法、混淆效果和已知的一些bug。同时,我们也会给出一些示例来演示混淆器的具体操作。 在信息竞赛训练和实际开发中,保护代码的安全性和保密性非常重要。C++代码

    2024年04月17日
    浏览(29)
  • C++实现一个异步日志库

    异步日志库(Asynchronous Logging Library)是一种用于记录应用程序运行时信息的库。相比于同步日志库,异步日志库能够提供更高的性能和可扩展性,因为它使用了异步写入和缓冲技术。 在异步日志库中,应用程序会将日志消息发送到一个缓存队列中,而不是直接写入到磁盘中

    2024年02月02日
    浏览(34)
  • C++ 多线程:实现一个功能完整的线程池

            今天我们来聊一聊异步编程的知识。在分布式系统中,一个功能完整的线程池类是一切代码的前提。 一个『合格』的线程池该具备哪些功能? 首先,很自然地想到『线程池类里该有个线程对象的集合,然后可以初始化线程对象的个数、创建线程对象、及启动线程主

    2024年02月04日
    浏览(42)
  • C++ 手写一个线程池

    本专栏已在我的个人站点中完成更新升级,可点击这里直达。 本专栏不再更新,不要购买!如有需要,请前往我的自建站点中购买, 价格更实惠 、 内容更丰富 、 并且继续保持更新 。 已购买该专栏的同学,可点击这里查看后续调整方案。 更多说明,可点击这里查看。

    2024年02月13日
    浏览(44)
  • C++实现一个线程安全的map

    本文是使用ChatCPT生成的,最终的代码使用起来没问题。代码是通过两轮对话完善的,后面把对话合并后跑不出理想效果就没尝试了。 c++11实现一个线程安全的map,使用方法与std::map保持一致,实现[]运算符 以下是一个简单的线程安全的map实现,可以使用[]运算符来访问和修改

    2024年02月03日
    浏览(40)
  • C++之深入解析如何实现一个线程池

    当进行并行的任务作业操作时,线程的建立与销毁的开销是,阻碍性能进步的关键,因此线程池,由此产生。使用多个线程,无限制循环等待队列,进行计算和操作,帮助快速降低和减少性能损耗。 线程池的组成: 线程池管理器:初始化和创建线程,启动和停止线程,调配

    2024年02月02日
    浏览(34)
  • C++ 多线程 线程安全队列设计

    这是看《C++并发编程实战》这本书学的,这里我要为这本书辟谣一下,虽然是这本书前面翻译得很烂,但是从第6章开始,应该是换了个人翻译,虽然还是能难懂,但是难懂的是代码逻辑,而不是语言逻辑; 实现,我先说明一下我自己的一个感悟,即对大多数线程错误的感悟

    2024年02月04日
    浏览(44)
  • C# 如何设计一个好用的日志库?【架构篇】

    相信你在实际工作期间经常遇到或听到这样的说法:   “我现在加一下日志,等会儿你再操作下。”   “只有在程序出问题以后才会知道打一个好的日志有多么重要。” 可见日志的记录是日常开发的必备技能。 记录日志的必要性:   当业务比较复杂时,在关键代码

    2023年04月17日
    浏览(39)
  • 设计一个支持并发的前端接口缓存

    目录 ​​​​​​​ 缓存池 并发缓存 问题 思考 优化🤔 总结 最后         缓存池不过就是一个 map ,存储接口数据的地方,将接口的路径和参数拼到一块作为 key ,数据作为 value 存起来罢了,这个咱谁都会。 封装一下调用接口的方法,调用时先走咱们缓存数据。 然后

    2024年02月07日
    浏览(42)
  • 算法的时间复杂度、空间复杂度如何比较?

    目录 一、时间复杂度BigO 大O的渐进表示法: 例题一: 例题2: 例题3:冒泡排序的时间复杂度 例题4:二分查找的时间复杂度 书写对数的讲究: 例题5:  实例6: 利用时间复杂度解决编程题 ​编辑思路一: 思路二: 源码: 思路三: 回顾位操作符 二、空间复杂度详解 概念

    2024年02月15日
    浏览(72)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包