协程库项目—日志模块

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日志配置SLF4J和Logback的方法实现

《SpringBoot日志配置SLF4J和Logback的方法实现》日志记录是不可或缺的一部分,本文主要介绍了SpringBoot日志配置SLF4J和Logback的方法实现,文中通过示例代码介绍的非... 目录一、前言二、案例一:初识日志三、案例二:使用Lombok输出日志四、案例三:配置Logback一

一文教你如何将maven项目转成web项目

《一文教你如何将maven项目转成web项目》在软件开发过程中,有时我们需要将一个普通的Maven项目转换为Web项目,以便能够部署到Web容器中运行,本文将详细介绍如何通过简单的步骤完成这一转换过程... 目录准备工作步骤一:修改​​pom.XML​​1.1 添加​​packaging​​标签1.2 添加

golang 日志log与logrus示例详解

《golang日志log与logrus示例详解》log是Go语言标准库中一个简单的日志库,本文给大家介绍golang日志log与logrus示例详解,感兴趣的朋友一起看看吧... 目录一、Go 标准库 log 详解1. 功能特点2. 常用函数3. 示例代码4. 优势和局限二、第三方库 logrus 详解1.

tomcat多实例部署的项目实践

《tomcat多实例部署的项目实践》Tomcat多实例是指在一台设备上运行多个Tomcat服务,这些Tomcat相互独立,本文主要介绍了tomcat多实例部署的项目实践,具有一定的参考价值,感兴趣的可... 目录1.创建项目目录,测试文China编程件2js.创建实例的安装目录3.准备实例的配置文件4.编辑实例的

如何自定义Nginx JSON日志格式配置

《如何自定义NginxJSON日志格式配置》Nginx作为最流行的Web服务器之一,其灵活的日志配置能力允许我们根据需求定制日志格式,本文将详细介绍如何配置Nginx以JSON格式记录访问日志,这种... 目录前言为什么选择jsON格式日志?配置步骤详解1. 安装Nginx服务2. 自定义JSON日志格式各

Python使用date模块进行日期处理的终极指南

《Python使用date模块进行日期处理的终极指南》在处理与时间相关的数据时,Python的date模块是开发者最趁手的工具之一,本文将用通俗的语言,结合真实案例,带您掌握date模块的六大核心功能... 目录引言一、date模块的核心功能1.1 日期表示1.2 日期计算1.3 日期比较二、六大常用方法详

springboot集成Deepseek4j的项目实践

《springboot集成Deepseek4j的项目实践》本文主要介绍了springboot集成Deepseek4j的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录Deepseek4j快速开始Maven 依js赖基础配置基础使用示例1. 流式返回示例2. 进阶

SpringBoot项目启动报错"找不到或无法加载主类"的解决方法

《SpringBoot项目启动报错找不到或无法加载主类的解决方法》在使用IntelliJIDEA开发基于SpringBoot框架的Java程序时,可能会出现找不到或无法加载主类com.example.... 目录一、问题描述二、排查过程三、解决方案一、问题描述在使用 IntelliJ IDEA 开发基于

SpringBoot项目使用MDC给日志增加唯一标识的实现步骤

《SpringBoot项目使用MDC给日志增加唯一标识的实现步骤》本文介绍了如何在SpringBoot项目中使用MDC(MappedDiagnosticContext)为日志增加唯一标识,以便于日... 目录【Java】SpringBoot项目使用MDC给日志增加唯一标识,方便日志追踪1.日志效果2.实现步

SQL Server清除日志文件ERRORLOG和删除tempdb.mdf

《SQLServer清除日志文件ERRORLOG和删除tempdb.mdf》数据库再使用一段时间后,日志文件会增大,特别是在磁盘容量不足的情况下,更是需要缩减,以下为缩减方法:如果可以停止SQLSe... 目录缩减 ERRORLOG 文件(停止服务后)停止 SQL Server 服务:找到错误日志文件:删除