协程库项目—日志模块

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

相关文章

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

SpringBoot如何使用TraceId日志链路追踪

《SpringBoot如何使用TraceId日志链路追踪》文章介绍了如何使用TraceId进行日志链路追踪,通过在日志中添加TraceId关键字,可以将同一次业务调用链上的日志串起来,本文通过实例代码... 目录项目场景:实现步骤1、pom.XML 依赖2、整合logback,打印日志,logback-sp

Python 中 requests 与 aiohttp 在实际项目中的选择策略详解

《Python中requests与aiohttp在实际项目中的选择策略详解》本文主要介绍了Python爬虫开发中常用的两个库requests和aiohttp的使用方法及其区别,通过实际项目案... 目录一、requests 库二、aiohttp 库三、requests 和 aiohttp 的比较四、requ

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.

Python项目打包部署到服务器的实现

《Python项目打包部署到服务器的实现》本文主要介绍了PyCharm和Ubuntu服务器部署Python项目,包括打包、上传、安装和设置自启动服务的步骤,具有一定的参考价值,感兴趣的可以了解一下... 目录一、准备工作二、项目打包三、部署到服务器四、设置服务自启动一、准备工作开发环境:本文以PyChar

多模块的springboot项目发布指定模块的脚本方式

《多模块的springboot项目发布指定模块的脚本方式》该文章主要介绍了如何在多模块的SpringBoot项目中发布指定模块的脚本,作者原先的脚本会清理并编译所有模块,导致发布时间过长,通过简化脚本... 目录多模块的springboot项目发布指定模块的脚本1、不计成本地全部发布2、指定模块发布总结多模