协程库项目—日志模块

2024-03-03 01:20
文章标签 模块 日志 项目 协程库

本文主要是介绍协程库项目—日志模块,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

日志模块程序结构图

sylarLog
├── LogLevel(日志级别封装类)
│   ├── 提供“从日志级别枚举值转换到字符串”、“从字符串转换相应的日志级别枚举值”等方法
├── LogEvent(日志事件类)
│   ├── 封装日志事件的属性,例如时间、线程id、日志等级、内容等等,并对外提供访问方法
│   └── 日志事件的构造在使用上会通过宏定义来简化
├── LogEventWrap(日志事件包装类)
│   ├── 内含日志事件 LogEvent
│   └── 日志事件在析构时由日志器进行输出
├── LogFormatter(日志格式类)
│   ├── 通过传递日志样式字符串给该类,该类对传入的字符串进行解析,例如 %d%t%p%m%n 表示 时间、线程号、日志等级、内容、换行
│   ├── 内含一个虚基类-日志内容格式化项 FormatItem
│   ├── 有13个子类,消息-MessageFormatItem、日志级别-LevelFormatItem、累计毫秒数-ElapseFormatItem、日志名称-NameFormatItem、线程id-ThreadIdFormatItem、换行-NewLineFormatItem、时间-DateTimeFormatItem、文件名-FilenameFormatItem、行号-LineFormatItem、Tab-TabFormatItem、协程id-FiberIdFormatItem、线程名称-ThreadNameFormatItem、直接打印字符串-StringFormatItem
│   └── 整个日志模块最复杂的逻辑就是该类解析日志样式的函数 init()
├── LogAppender(日志输出目的地类)
│   ├── LogAppender 为虚基类,有纯虚函数,留给子类去各自实现
│   ├── 实现的子类如 StdoutLogAppender 和 FileLogAppender
│   └── Appender 自带一个默认的 LogFormatter,以默认方式输出
├── StdoutLogAppender(标准化输出类)
│   └── 日志输出到控制台
├── FileLogAppender(文件输出类)
│   └── 日志输出到相应的文件中
├── Logger(日志器类)
│   ├── 设置日志名称、设置日志等级 LogLevel、设置日志输出位置 LogAppender、设置日志格式、根据日志级别控制日志输出等
├── LoggerManager(日志管理器类)
│   ├── 利用 map 存放各个 Logger 实例,其中 key 是日志器的名称,value 是日志器的智能指针
│   └── 还内含一个主日志器 root
└── 其他说明├── 每个类都 typedef std::shared_ptr ptr,方便外界使用其智能指针└── 普遍使用 Spinlock 实现互斥,保证线程安全。Spinlock 比 普通的 Mutex 效率高,但耗CPU。

数据流转

  1. 首先通过SYLAR_LOG_NAME(name)宏从LoggerMgr中获取对应的Logger对象,然后通过SYLAR_LOG_DEBUG(logger)->SYLAR_LOG_LEVEL(logger,level)宏创建一个新的LogEvent对象,并将其传递给LogEventWrap临时对象。接着,通过std::stringstream将日志内容存入其中。
  2. 当LogEventWrap临时对象析构时,会调用Logger的log方法,遍历其所有的LogAppender,并调用每个LogAppender的log方法(传入event参数)。
  3. 这里以FileLogAppender为例,LogAppender的log方法会加上自己的std::ostream参数(如果是输出到控制台,则是std::cout),然后调用LogFormatter的format方法(传入ostream、event参数)。
  4. LogFormatter的format方法会遍历自己缓存的所有FormatItem(继承了FormatItem的各种子类智能指针),将日志内容格式化(例如加上时间日期、线程id等)。
  5. 调用的是每个FormatItem的format方法(传入ostream、event参数)。最后,每个FormatItem的format方法会将格式化后的内容以流式方式存入std::ostream。如果是输出到控制台,那么这里就直接输出了。如果是文件,因为std::ostream关联了文件,因此会对文件进行缓存写(非实时写)。

LogFormatter类的init方法

LogFormatter类的init方法,用于解析日志格式字符串。主要功能如下:

首先,定义了一个patterns向量,用于存储解析到的模式项。每个模式项包括一个整数类型和一个字符串,类型为0表示该模式是常规字符串,为1表示该模式需要转义。
定义了一个临时变量tmp,用于存储常规字符串。
定义了一个日期格式字符串dateformat,默认把位于%d后面的大括号对里的全部字符都当作格式字符,不校验格式是否合法。
定义了一个布尔变量error,用于标记解析过程中是否出错。
定义了两个布尔变量parsing_string和parsing_pattern,分别表示是否正在解析常规字符和模板字符。初始时,parsing_string为true。
使用一个循环遍历m_pattern字符串中的每个字符,根据不同的情况进行解析。
如果当前字符是"%“,则根据parsing_string的值进行不同的处理。如果正在解析常规字符,则将之前的常规字符串添加到patterns中,并将parsing_string设置为false;如果正在解析模板字符,则将当前的”%“作为模板字符添加到patterns中,并将parsing_string设置为true。
如果当前字符不是”%“,则根据parsing_string的值进行不同的处理。如果正在解析常规字符,则将当前字符添加到tmp中;如果正在解析模板字符,则将当前字符作为模板字符添加到patterns中,并根据不同情况进行特殊处理。
在解析模板字符的过程中,如果遇到”%d",则需要进一步解析日期格式字符串。通过遍历后续字符,直到找到闭合的大括号,将其中的字符添加到dateformat中。
如果在解析过程中出现错误,将m_error设置为true并返回。
最后,根据解析得到的模式项创建相应的格式化项对象,并将其添加到m_items中。
如果解析过程中出现错误,将m_error设置为true并返回。

void LogFormatter::init() {// 按顺序存储解析到的pattern项// 每个pattern包括一个整数类型和一个字符串,类型为0表示该pattern是常规字符串,为1表示该pattern需要转义// 日期格式单独用下面的dataformat存储std::vector<std::pair<int, std::string>> patterns;// 临时存储常规字符串std::string tmp;// 日期格式字符串,默认把位于%d后面的大括号对里的全部字符都当作格式字符,不校验格式是否合法std::string dateformat;// 是否解析出错bool error = false;// 是否正在解析常规字符,初始时为truebool parsing_string = true;// 是否正在解析模板字符,%后面的是模板字符// bool parsing_pattern = false;size_t i = 0;while(i < m_pattern.size()) {std::string c = std::string(1, m_pattern[i]);if(c == "%") {if(parsing_string) {if(!tmp.empty()) {patterns.push_back(std::make_pair(0, tmp));}tmp.clear();parsing_string = false; // 在解析常规字符时遇到%,表示开始解析模板字符// parsing_pattern = true;i++;continue;} else {patterns.push_back(std::make_pair(1, c));parsing_string = true; // 在解析模板字符时遇到%,表示这里是一个%转义// parsing_pattern = false;i++;continue;}} else { // not %if(parsing_string) { // 持续解析常规字符直到遇到%,解析出的字符串作为一个常规字符串加入patternstmp += c;i++;continue;} else { // 模板字符,直接添加到patterns中,添加完成后,状态变为解析常规字符,%d特殊处理patterns.push_back(std::make_pair(1, c));parsing_string = true; // parsing_pattern = false;// 后面是对%d的特殊处理,如果%d后面直接跟了一对大括号,那么把大括号里面的内容提取出来作为dateformatif(c != "d") {i++;continue;}i++;if(i < m_pattern.size() && m_pattern[i] != '{') {continue;}i++;while( i < m_pattern.size() && m_pattern[i] != '}') {dateformat.push_back(m_pattern[i]);i++;}if(m_pattern[i] != '}') {// %d后面的大括号没有闭合,直接报错std::cout << "[ERROR] LogFormatter::init() " << "pattern: [" << m_pattern << "] '{' not closed" << std::endl;error = true;break;}i++;continue;}}} // end while(i < m_pattern.size())if(error) {m_error = true;return;}// 模板解析结束之后剩余的常规字符也要算进去if(!tmp.empty()) {patterns.push_back(std::make_pair(0, tmp));tmp.clear();}// for debug // std::cout << "patterns:" << std::endl;// for(auto &v : patterns) {//     std::cout << "type = " << v.first << ", value = " << v.second << std::endl;// }// std::cout << "dataformat = " << dateformat << std::endl;static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)> > s_format_items = {
#define XX(str, C)  {#str, [](const std::string& fmt) { return FormatItem::ptr(new C(fmt));} }XX(m, MessageFormatItem),           // m:消息XX(p, LevelFormatItem),             // p:日志级别XX(c, LoggerNameFormatItem),        // c:日志器名称
//        XX(d, DateTimeFormatItem),          // d:日期时间XX(r, ElapseFormatItem),            // r:累计毫秒数XX(f, FileNameFormatItem),          // f:文件名XX(l, LineFormatItem),              // l:行号XX(t, ThreadIdFormatItem),          // t:编程号XX(F, FiberIdFormatItem),           // F:协程号XX(N, ThreadNameFormatItem),        // N:线程名称XX(%, PercentSignFormatItem),       // %:百分号XX(T, TabFormatItem),               // T:制表符XX(n, NewLineFormatItem),           // n:换行符
#undef XX};//根据解析得到的模式项创建相应的格式化项对象,并将其添加到m_items中。for(auto &v : patterns) {if(v.first == 0) {m_items.push_back(FormatItem::ptr(new StringFormatItem(v.second)));} else if( v.second =="d") {m_items.push_back(FormatItem::ptr(new DateTimeFormatItem(dateformat)));} else {auto it = s_format_items.find(v.second);if(it == s_format_items.end()) {std::cout << "[ERROR] LogFormatter::init() " << "pattern: [" << m_pattern << "] " << "unknown format item: " << v.second << std::endl;error = true;break;} else {m_items.push_back(it->second(v.second));}}}if(error) {m_error = true;return;}
}

这篇关于协程库项目—日志模块的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/768018

相关文章

springboot项目中整合高德地图的实践

《springboot项目中整合高德地图的实践》:本文主要介绍springboot项目中整合高德地图的实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一:高德开放平台的使用二:创建数据库(我是用的是mysql)三:Springboot所需的依赖(根据你的需求再

一文深入详解Python的secrets模块

《一文深入详解Python的secrets模块》在构建涉及用户身份认证、权限管理、加密通信等系统时,开发者最不能忽视的一个问题就是“安全性”,Python在3.6版本中引入了专门面向安全用途的secr... 目录引言一、背景与动机:为什么需要 secrets 模块?二、secrets 模块的核心功能1. 基

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

MySQL版本问题导致项目无法启动问题的解决方案

《MySQL版本问题导致项目无法启动问题的解决方案》本文记录了一次因MySQL版本不一致导致项目启动失败的经历,详细解析了连接错误的原因,并提供了两种解决方案:调整连接字符串禁用SSL或统一MySQL... 目录本地项目启动报错报错原因:解决方案第一个:第二种:容器启动mysql的坑两种修改时区的方法:本地

Golang 日志处理和正则处理的操作方法

《Golang日志处理和正则处理的操作方法》:本文主要介绍Golang日志处理和正则处理的操作方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录1、logx日志处理1.1、logx简介1.2、日志初始化与配置1.3、常用方法1.4、配合defer

springboot项目中使用JOSN解析库的方法

《springboot项目中使用JOSN解析库的方法》JSON,全程是JavaScriptObjectNotation,是一种轻量级的数据交换格式,本文给大家介绍springboot项目中使用JOSN... 目录一、jsON解析简介二、Spring Boot项目中使用JSON解析1、pom.XML文件引入依

使用vscode搭建pywebview集成vue项目实践

《使用vscode搭建pywebview集成vue项目实践》:本文主要介绍使用vscode搭建pywebview集成vue项目实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录环境准备项目源码下载项目说明调试与生成可执行文件核心代码说明总结本节我们使用pythonpywebv

Apache 高级配置实战之从连接保持到日志分析的完整指南

《Apache高级配置实战之从连接保持到日志分析的完整指南》本文带你从连接保持优化开始,一路走到访问控制和日志管理,最后用AWStats来分析网站数据,对Apache配置日志分析相关知识感兴趣的朋友... 目录Apache 高级配置实战:从连接保持到日志分析的完整指南前言 一、Apache 连接保持 - 性

Maven项目中集成数据库文档生成工具的操作步骤

《Maven项目中集成数据库文档生成工具的操作步骤》在Maven项目中,可以通过集成数据库文档生成工具来自动生成数据库文档,本文为大家整理了使用screw-maven-plugin(推荐)的完... 目录1. 添加插件配置到 pom.XML2. 配置数据库信息3. 执行生成命令4. 高级配置选项5. 注意事