Log库和配置系统结构

news2024/10/6 10:31:22

Log库:

类关系

首先有3个大类:LogEvent、LogAppender、Logger、LogFormat;

关系如下:

        

Logger:具体log的实现

LogAppender:将Log信息传输到不同的目的地,根据不同的需求派生出不同的类

LogFormat:存储格式信息,并根据LogEvent生成信息

log Event:存储具体的log信息,如内容、时间戳、线程号等等

类实现:

LogFormat:格式由%[ item ]{ [data] }构成

首先对于每一个item,我的方法是通过策略模式来解决,每一个item都继承自FormatItem,比如输出时间信息的TimeFormatItem,输出线程号的ThreadIDFormatItem,每一种不同的信息都用不同的策略;

然后将格式字符串解析出来,根据不同的格式生成不同的策略,最后在接收LogEvent的时候从中提取信息到每一个Item中,并生成最后的Log信息

		class FormatItem {
		public:
			using Ptr = std::shared_ptr<FormatItem>;
			virtual ~FormatItem(){}
            //os存储最后的字符串
			virtual void format(std::ostream& os, Logger*, LogLevel, LogEvent::Ptr) = 0;
		};

比如%d %m解析为输出时间和内容;

LogEvent

LogEvent就是一个存储信息的地方,在这一方面没什么好说的,一堆变量,我们可以让LogEvent支持c风格的printf,这样我们可以将处理后的字符串作为内容:

	class LogEvent {
	public:
		//"xxxx %s" ,"string" 的形式会在void Format(const char* fmt, va_list al, bool isStart = false);进行,因为va_list是char*,
		//所以设计一个Flag标记,防止错误载入
		struct Flag{};
		using Ptr = std::shared_ptr<LogEvent>;
		LogEvent(std::shared_ptr<Logger> logger, LogLevel level
			, const char* file, int32_t m_line, uint32_t elapse
			, uint32_t thread_id, uint32_t fiber_id, uint64_t time);


		const std::string GetContent() { return m_Content.str(); }
		std::stringstream& GetStream() { return m_Content; }
		uint32_t GetElapse() { return m_elaspe; }
		uint64_t GetTime() { return m_time; }
		uint32_t GetThreadID() { return m_ThreadId; }
		uint32_t GetLine() { return m_Line; }
		uint32_t GetFiberID() { return m_FiberId; }
		const char* GetFile() { return m_file; }
		LogLevel GetLevel() { return m_Level; }
		std::shared_ptr<Logger>& GetLogger() { return m_Logger; }

		void Format(const char* fmt, ...);

	private:
		void Format(const char* fmt, Flag ,va_list al, bool isStart = false);
		const char* m_file = nullptr;	//文件名
		std::stringstream m_Content;			//内容
		int32_t m_Line = 0;				//行号
		uint32_t m_ThreadId = 0;		//线程号
		uint32_t m_FiberId = 0;			//协程号
		uint64_t m_time = 0;			//时间戳
		uint32_t m_elaspe = 0;			//从重新开始到目前的时间

		std::shared_ptr<Logger> m_Logger;
		LogLevel m_Level;

		friend class LogFormatter;
	};

支持c风格的格式化:

	void LogEvent::Format(const char* fmt, ...) {
		va_list al;
		va_start(al, fmt);
		Format(fmt, Flag{},al, true);
		va_end(al);
	}
	void LogEvent::Format(const char* fmt, Flag ,va_list al,bool isStart) {
		char* buf = nullptr;
		int len = vasprintf(&buf, fmt, al);
		if (len != -1) {
			m_Content << buf;
			free(buf);
		}
	}

注意,如果是Windows的话vasprint要自己去实现一下:

#ifdef _WIN32
	int vasprintf(char** strp, const char* fmt, va_list ap){
		va_list ap_copy;
		va_copy(ap_copy, ap);
		int len = vsnprintf(NULL, 0, fmt, ap);
		if (len < 0) {
			return -1;
		}
		*strp = (char*)malloc(len + 1);
		if (*strp == NULL) {
			return -1;
		}
		len = vsnprintf(*strp, len + 1, fmt, ap_copy);
		va_end(ap_copy);
		return len;
	}

	int asprintf(char** ptr, const char* format, ...) {
		va_list ap;
		int ret;

		*ptr = NULL;

		va_start(ap, format);
		ret = vasprintf(ptr, format, ap);
		va_end(ap);

		return ret;
	}
#endif

LogAppender

这个的话,这个类有很多种实现方法,具体的思路就是持有一个LogFormat,然后给它一个LogEvent,将生成出来的字符串加入到目的地,如文件或者控制台;

Logger

这个也非常好弄,他就是一个LogAppender的集合,将一个LogEvent分发给不同的LogAppender,实现一个信息分发到不同目的地;

End

也可以写一个Manger类,来管理所有的Logger,这个结构呢,就是怎么解析字符串,然后生成不同的item比较难,其他都是顺水推舟了;

Config系统:

使用了YAML-CPP库

Config系统的话,有3大类和两大操作:

ConfigVarBase、ConfigVar<T>:public ConfigVarBase、Config

两大操作:FormString、ToString

ConfigVarBase

ConfigVarBase就一个接口,没什么好说的,看代码:

	class ConfigVarBase {
	public:
		using Ptr = std::shared_ptr<ConfigVarBase>;
		ConfigVarBase(const std::string& name,const std::string& description = "")
			:m_Name(name), m_Description(description) {
			//To lower
			std::transform(name.begin(), name.end(), m_Name.begin(), ::tolower);
		}
		virtual ~ConfigVarBase(){}
		const std::string& GetName()const { return m_Name; };
		const std::string& GetDescription()const {return m_Description;};

		virtual std::string ToString() = 0;
		virtual bool FromString(const std::string& val) = 0;
	protected:
		std::string m_Name;
		std::string m_Description;
	};

ConfigVar<T>

然后是ConfigVar<T>了,这个类的话,是一个模板,存储不同类型的信息,对应不同的配置需要,比如存储一个vector或者map。

我们会有一个ToString和FromString的操作,这俩呢,就是序列化和反序列话,将信息存储到文件中,然后从文件在加载信息,一段信息经过两次操作不能改变;

	template<class T,
		class FormSting_ = LexicalCast<std::string, T>,
			class ToString_ = LexicalCast<T, std::string>>
	class ConfigVar :public ConfigVarBase {
		FormSting_ formStr;
		ToString_ toStr;
	public:
		using Ptr = std::shared_ptr<ConfigVar<T>>;

		ConfigVar(const std::string& name, const T& defaultVal, const std::string& description = "")
			:ConfigVarBase(name, description), m_Val(defaultVal){}

		std::string ToString() override {
			try {
				return toStr(m_Val);
			}
			catch (std::exception& e) {
				LU_LOG_ERROR(LU_LOG_ROOT()) << "ConfigVar::ToString expection" <<
					e.what() << " convert :" << typeid(m_Val).name() << " to string";
			}
		}

		const T& GetValue() { return m_Val; }

		static std::string GetTypeName() { return typeid(T).name(); }

		virtual bool FromString(const std::string& val) override {
			try {
				m_Val = formStr(val);
				return true;
			}
			catch (std::exception& e) {
				LU_LOG_ERROR(LU_LOG_ROOT()) << "ConfigVar::ToString expection" <<
					e.what() << " convert :" << "string to "<< typeid(m_Val).name();
				return false;
			}
		}

	private:
		T m_Val;
	};

这边我们用LexicalCast将ToString和FormString分离到类外去操作,这样可以增加代码的可读性和可拓展性;

Config

这个类是最主要的一个类,作用是管理ConfigVar和读取文件,并根据读取的信息设置配置;

	class Config {
	public:
		using Ptr = std::shared_ptr<Config>;

		template<class T>
		static typename ConfigVar<T>::Ptr LookUp(const std::string& name, 
			const T& defaultValue, const std::string& description) {
			typename ConfigVar<T>::Ptr config = LookUp<T>(name);
			if (config) {
				return config;
			}
			if (name.find_first_not_of("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM._0123456789") != std::string::npos) {
				LU_LOG_ERROR(LU_LOG_ROOT()) << "lookup name invaild :" << name;
				throw std::invalid_argument(name);
			}

			typename ConfigVar<T>::Ptr v(new ConfigVar<T>(name, defaultValue, description));
			map.emplace(name, v);
			return std::dynamic_pointer_cast<ConfigVar<T>>(map.at(name));
		}

		template<class T>
		static typename ConfigVar<T>::Ptr LookUp(const std::string& name) {
			auto it = map.find(name);
			if (it != map.end()) {
				ConfigVar<T>::Ptr res = std::dynamic_pointer_cast<ConfigVar<T>>(it->second);
				if (!res) 
					LU_LOG_ERROR(LU_LOG_ROOT()) << "Lookup name :" << name << 
					" exist but type not is " << ConfigVar<T>::GetTypeName() << "  " << it->second->ToString();
				else
					LU_LOG_INFO(LU_LOG_ROOT()) << "Lookup name :" << name << " exist";
				return res;
			}
			LU_LOG_ERROR(LU_LOG_ROOT()) << "Lookup name :" << name << " not exist";
			return nullptr;
		}

		static ConfigVarBase::Ptr LookupBase(const std::string& name);

		static void LoadFormYaml(const YAML::Node& root);
		
	private:

		static std::unordered_map<std::string, ConfigVarBase::Ptr> map;

	};

这边的话主要是看LoadFormYaml这个方法,这个方法是接受一个Node节点,这个节点是存储被读取文件的整个信息,读取数据后根据数据设置map中的ConfigVar:

	static void ListAllMember(const std::string& prefix,
		const YAML::Node& node, 
		std::list<std::pair<std::string, const YAML::Node>>& output) {
		if (prefix.find_first_not_of("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM._0123456789") != std::string::npos) {
			LU_LOG_ERROR(LU_LOG_ROOT()) << "Congig invaild name " << prefix << " : " << node;
			return;
		}

		/*
		* Scalar是直接读取字符串形式的数据,
		* 构建     x
		*		  / \
		*      x.y   x.z
		*       |
		*      x.y.u
		*/
		output.push_back({ prefix,node });
		if (node.IsMap()) {
			for (auto& t : node) {

				ListAllMember(prefix.empty()?
					t.first.Scalar() : prefix + "." + t.first.Scalar(),
					t.second, output);
			}
		}

	}

	void Config::LoadFormYaml(const YAML::Node& root) {
		std::list<std::pair<std::string, const YAML::Node>> allNodes;
		ListAllMember("",root, allNodes);

		for (auto& it : allNodes) {
			std::string key = it.first;
			if (key.empty())
				continue;

			std::transform(key.begin(), key.end(), key.begin(), ::tolower);
			ConfigVarBase::Ptr var = LookupBase(key);
			if (var) {
				if (it.second.IsScalar()) {
					var->FromString(it.second.Scalar());
				}
				else {
					std::stringstream ss;
					ss << it.second;
					var->FromString(ss.str());
				}
			}
		}
	}

主要是实现这两个函数,ListAllMember呢,是将root node中的所有节点都加入到一个list中,而且名字也有讲究,ConfigVar的name就root node展开的树中对应该ConfigVar节点的路径,路径名字的格式已经在代码中给出了,根据这个名字我们可以给每个ConfigVar设置数据;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/416357.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java 系列 Nacos

Java 系列文章 文章目录Java 系列文章前言一、Nacas 介绍及安装1. 什么是Nacos2. 为什么使用Nacos3. Nacos 下载和安装二、Nacos服务提供者注册1. Nacos代替Eureka2. Nacos服务注册中心3. Nacos Discovery引入1. 创建新项目2. POM3. YML文件4. 启动类5. 业务类6. 测试&#xff…

Git如何推送当前代码到远程仓库

第一种方法 &#xff08;建立在已经配置好用户变量和ssh基础上&#xff09; 在本地创建git仓库 git init 绑定远程仓库&#xff0c;origin是给远程仓库起的别名&#xff0c;也可以起其他名字&#xff0c;但是如果用origin&#xff0c;git push时可以不指出名字&#xff0c;如果…

【2023 · CANN训练营第一季】昇腾AI入门课(Pytorch)——第一章学习笔记

第一章 昇腾AI基础知识介绍 第2节 昇腾AI全栈架构 昇腾 AI 全栈可以分成四个大部分&#xff1a; 1&#xff0e;应用使能层面&#xff0c;此层面通常包含用于部署模型的软硬件&#xff0c;例如 API 、 SDK 、部署平台&#xff0c;模型库等等。 2. AI 框架层面&#xff0c;此层…

【C语言】 程序员的自我修养之(程序编译过程)

在ANSI C(标准C)的任何一种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境&#xff0c;它用于实际执行代码。 今天我们就讲解他们在这环境过程都做了什么。 文章目录详解编译链接翻译环境编…

【数据库原理 • 七】数据库并发控制

前言 数据库技术是计算机科学技术中发展最快&#xff0c;应用最广的技术之一&#xff0c;它是专门研究如何科学的组织和存储数据&#xff0c;如何高效地获取和处理数据的技术。它已成为各行各业存储数据、管理信息、共享资源和决策支持的最先进&#xff0c;最常用的技术。 当前…

【19】核心易中期刊推荐——人工智能 | 遥感信息处理

🚀🚀🚀NEW!!!核心易中期刊推荐栏目来啦 ~ 📚🍀 核心期刊在国内的应用范围非常广,核心期刊发表论文是国内很多作者晋升的硬性要求,并且在国内属于顶尖论文发表,具有很高的学术价值。在中文核心目录体系中,权威代表有CSSCI、CSCD和北大核心。其中,中文期刊的数…

redis——优化

键值设计bigKey例子批处理单机 pipeline集群服务器持久化慢查询安全内存集群问题集群完整性集群带宽数据倾斜客户端性能命令的集群兼容性lua和事务&#xff1a;集群下不支持键值设计 长度 < 44 节省内存。string的底层数据结构中&#xff0c;编码格式embstr&#xff08;连续…

LeetCode:455. 分发饼干——贪心算法

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340;算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 贪心算法是在每个阶段选取局部最优解&#xff0c;最终得到全局最优解的一种思想。贪心算法…

操作系统论文导读(四):Minimizing Memory Utilization of Real-Time Task Sets in Single and…

目录 一、论文核心思想&#xff1a; 二、降低RAM的思想 三、基本的相关定义 四、单处理器方面 五、优化单处理器中的堆栈使用 六、多处理器方面 七、基本的相关调度 八、协议特点 Minimizing Memory Utilization of Real-Time Task Sets in Single and Multi-Processor…

算法记录 | Day29 回溯算法

491.递增子序列 思路&#xff1a; 1.确定回溯函数参数&#xff1a;定义全局遍历存放res集合和单个path&#xff0c;还需要 nums数组startindex&#xff08;int&#xff09;为下一层for循环搜索的起始位置。 2.终止条件&#xff1a;当startindex >len(nums)&#xff0c;r…

C++初阶—vector深度剖析及模拟实现

目录 ➡️0. 前言 &#x1f60a;1.简易框架实现 &#x1f414;1. 无参构造 &#x1f414;2. 容量capacity — 长度size() &#x1f414;3. 动态增长 — push_back—pop_back — reserve &#x1f414;4. 迭代器的实现 &#x1f414;4.front和back的实现 &#x1f60a;2…

你知道C语言的typedef关键字吗?

本篇博客主要讲解C语言中的typedef关键字。typedef的作用是类型重定义&#xff0c;可以理解为给类型起一个别名。我主要从3个方面来讲解&#xff1a; typedef内置类型。typedef自定义类型。typedef和#define的区别。 1.typedef内置类型 typedef可以给一个类型起“别名”。比如…

服务器部署前后端分离项目

服务器部署前后端分离项目 目录服务器部署前后端分离项目一、安装环境安装jdk1、在/usr/local目录下创建jdk文件夹&#xff0c;并将jdk安装包放到/usr/local/jdk包下并解压1.1通过文件传输工具将jdk包上传到服务器上1.2输入解压命令1.3解压完成&#xff0c;生成下面的文件2、配…

学习周报4/9

文章目录前言文献阅读摘要简介方法结论时间序列预测总结前言 本周阅读文献《Improving LSTM hydrological modeling with spatiotemporal deep learning and multi-task learning: A case study of three mountainous areas on the Tibetan Plateau》&#xff0c;文章主要基于…

多种方法解决SLF4J: Defaulting to no-operation (NOP) logger implementation的错误

文章目录1. 复现错误2. 分析错误3. 解决错误4. 解决该错误的其他方法1. 复现错误 今天在编写使用Quartz执行定时任务的方法&#xff0c;如下代码所示&#xff1a; public class QuartzTest {public static void main(String[] args) throws SchedulerException {// 1、创建Sch…

大数据系列——Hive理论

概述 Hive是一个数据仓库管理工具&#xff0c;将结构化的数据文件映射为一张数据库表&#xff0c;并提供类SQL&#xff08;HQL&#xff09;查询功能。由Facebook实现并开源,最后捐赠给Apache发展为顶级项目。 以RDBMS数据库为元数据存储服务&#xff0c; 以Hadoop HDFS来存储…

44.节流与防抖

目录 1 防抖 1.1 概念 1.2 应用场景 1.3 lodash防抖 1.4 手写防抖 2 节流 2.1 概念 2.2 应用场景 2.3 lodash节流 2.4 手写节流 2.5 记录视频上一次的播放位置 1 防抖 1.1 概念 防抖就是让事件触发后延迟n秒后再执行回调函数&#xff0c;在这n秒内如…

014:Mapbox GL添加draw组件,绘制点、线、多边形、删除

第014个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中添加draw组件,绘制点、线、多边形,删除所选元素。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共78行)相关API参考:专栏目标示例效果 配置方…

用于平抑可再生能源功率波动的储能电站建模及评价(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

项目部署---手工部署项目

手工部署项目 在ideal中开发springboot项目并打成jar包 将jar包上传到Linux服务器 mkdir /usr/local/app 创建目录&#xff0c;将项目jar包放到此目录 ![](https://img-blog.csdnimg.cn/83cf26b151874637a2dfeda7dd05e4cf.jpeg) 启动SpringBoot程序 检查防火墙&#xff0c;…