重写Sylar基于协程的服务器(1、日志模块的架构)

2024-01-31 08:28

本文主要是介绍重写Sylar基于协程的服务器(1、日志模块的架构),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

重写Sylar基于协程的服务器(1、日志模块的架构)

重写Sylar基于协程的服务器系列:

重写Sylar基于协程的服务器(0、搭建开发环境以及项目框架 || 下载编译简化版Sylar)

重写Sylar基于协程的服务器(1、日志模块的架构)

前言

和Muduo的日志对比,Muduo的同步日志虽然格式固定,但简单高性能。而sylar的日志设计的显得过于冗余,虽然灵活性强、扩展性高,但是性能却不及Muduo。尽管陈硕大大也说过,简单才能保证高性能,日志就没必要设计的那么花里胡哨,但是sylar对日志的设计思想以及设计模式超级超级适合小白去学习。

日志格式

由于用户可能并不需要将日志上下文的每一项都进行输出,而是希望可以自由地选择要输出的日志项。并且,用户还可能需要在每条日志里增加一些指定的字符,比如在文件名和行号之间加上一个冒号的情况。为了实现这项功能,LogFormatter使用了一个模板字符串来指定格式化的方式。模板字符串由普通字符和占位字符构成。在构造LogFormatter对象时会指定一串模板字符,LogFormatter会首先解析该模板字符串,将其中的占位符和普通字符解析出来。在格式化日志上下文时,根据模板字符串,将其中的占位符替换成日志上下文的具体内容,普通字符保持不变。下表是支持的占位符的含义。

占位符含义
%s普通字符串(直接输出的字符串)
%d时间
%t线程真实id
%N线程名
%f协程id
%p日志级别
%c日志器名
%F文件路径
%l行号
%m日志消息
%TTab缩进
%n换行

%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%f%T[%p]%T[%c]%T%F:%l%T%m%n格式串为例,输出效果如图下:

在这里插入图片描述

时间占位符%d需要带有格式化参数%Y-%m-%d %H:%M:%S,这使得日志格式器能对日志上下文收集到的时间戳进行格式化,而对于其他占位符,日志格式器只需要从日志上下文中取来做简单的处理再直接拼接即可。

日志模块架构设计

sylar实现的日志中,一条日志数据流向是:日志包装器->日志器->数个日志输出地,如图所示。

在这里插入图片描述

关于这几个类的设计如下:

  1. LogEvent: 日志上下文,用于记录日志现场,比如该日志的时间,文件路径/行号,日志级别,线程/协程号,日志消息等。

  2. LogEventWrap: 日志事件包装类,其实就是将日志事件和日志器一起包装到日志上下文中,因为一条日志只会在一个日志器上进行输出。将日志事件和日志器包装到一起后,方便通过宏定义来简化日志模块的使用(这点和Muduo很像)。另外,LogEventWrap还负责在构建时指定日志事件和日志器,在析构时调用日志器的log方法将日志事件进行输出。

  3. Logger: 日志器,负责进行日志输出。一个Logger包含多个LogAppender和一个日志级别,提供log方法,传入日志事件,如果日志事件的级别高于日志器本身的级别就调用每一个LogAppender的log方法将日志进行输出,否则该日志被抛弃。

    日志包装器以及日志器伪代码:

    在这里插入图片描述

  4. LogAppender: 日志输出器,用于将一个日志上下文输出到对应的输出地。该类内部包含一个LogFormatter成员和一个log方法,日志事件先经过LogFormatter格式化后再输出到对应的输出地。从这个类可以派生出不同的输出器类型,比如StdoutLogAppender和FileLogAppender,分别表示终端和文件的日志输出器。

    日志输出器伪代码:

    在这里插入图片描述

  5. LogFormatter: 日志格式器,用于格式化一个日志事件。该类构建时可以指定pattern并根据提供的pattern调用init()进行解析。提供format方法,用于将日志事件格式化成字符串。

    日志格式串的解析:

        void LogFormatter::init(){std::vector<std::tuple<std::string, std::string>> res;int len = m_pattern.length();//state -- 0 普通字符部分/日志修饰字符//state -- 1 格式化字符部分//state -- 2 格式化字符参数部分int pLt = 0, pRt = 0, state = 0 ;//'\0'看成万能字符while(pRt <= len){if(state == 0){//状态升级if(pRt == len || m_pattern[pRt] == '%'){if(pLt < pRt){res.push_back(std::make_tuple("s", m_pattern.substr(pLt, pRt - pLt)));}state = 1;  //升级pLt = pRt + 1;}}else if(state == 1){//状态还原 或 状态升级 //或 此时遇到非{,非%,非字母的字符,则隐式代表格式化字符部分结束if(pRt < len && m_pattern[pRt] == '{'){if(pLt < pRt){res.push_back(std::make_tuple(m_pattern.substr(pLt, pRt - pLt), ""));}else{//错误:没有模式字符只有选项参数res.push_back(std::make_tuple("s", "<parse error> empty format character : "));}state = 2;pLt = pRt + 1;}else if(pRt < len && m_pattern[pRt] == '%'){if(pLt < pRt){res.push_back(std::make_tuple(m_pattern.substr(pLt, pRt - pLt), ""));}state = 0;pLt = pRt;continue;}else if(pRt == len || !isalpha(m_pattern[pRt])){if(pLt < pRt){res.push_back(std::make_tuple(m_pattern.substr(pLt, pRt - pLt), ""));}state = 0;pLt = pRt;}}else{  //state == 2//状态还原//缺少},结尾("\0")默认为'}'if(pRt == len || m_pattern[pRt] == '}'){std::get<1>(res.back()) = std::get<1>(res.back()) + m_pattern.substr(pLt, pRt - pLt);state = 0;pLt = pRt + 1;}}pRt++;}// ... 省略不重要的部分}
    
  6. LogManager: 日志器管理类,单例模式,用于统一管理所有的日志器,提供日志器的创建与获取方法。LogManager自带一个root Logger,用于为日志模块提供一个初始可用的日志器。

简化日志使用的宏定义:

日志宏:

#define LUNAR_LOG_LEVEL(logger, level) \if((logger)->getLevel() <= level) \(lunar::LogEventWrap(lunar::LogEvent::ptr(new lunar::LogEvent(__FILE__, __LINE__,\lunar::GetElapse(), lunar::Thread::GetTid(),\lunar::GetFiberId(), ::time(nullptr),\lunar::Thread::GetName(), level, (logger))))).getMsg()#define LUNAR_LOG_DEBUG(logger) LUNAR_LOG_LEVEL(logger, lunar::LogLevel::Level::DEBUG)#define LUNAR_LOG_INFO(logger) LUNAR_LOG_LEVEL(logger, lunar::LogLevel::Level::INFO)#define LUNAR_LOG_WRONG(logger) LUNAR_LOG_LEVEL(logger, lunar::LogLevel::Level::WRONG)#define LUNAR_LOG_ERROR(logger) LUNAR_LOG_LEVEL(logger, lunar::LogLevel::Level::ERROR)#define LUNAR_LOG_FATAL(logger) LUNAR_LOG_LEVEL(logger, lunar::LogLevel::Level::FATAL)

其他

关于异步日志

本着基于协程淡化线程的思想,在sylar中,要实现异步日志的前提是,先要基于协程实现一个信号量,然后通过继承LogAppender参考Muduo设计一个异步日志,但是其后台是由一个协程进行落盘

异步日志的实现涉及到后面的fiber、hook等模块,本文简略带过。

异步日志和同步日志对比

优点缺点
异步日志执行效率更高,一般应用程序只用将日志输出到一块缓存中,由另起的线程将缓存中的日志输出到磁盘上,减少了系统的IO负担当系统崩溃时,容易丢失内存中来不及写入的日志。日志本身的代码实现、调试更复杂
同步日志事件发生就输出,系统崩溃不会出现丢日志的情况,日志输出顺序可控,代码实现简单效率更低,增加系统IO负担,输出日志时会阻塞工作线程

感兴趣的同学,可以阅读一下本文实现的源码:https://github.com/LunarStore/lunar


本章完结

这篇关于重写Sylar基于协程的服务器(1、日志模块的架构)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Window Server创建2台服务器的故障转移群集的图文教程

《WindowServer创建2台服务器的故障转移群集的图文教程》本文主要介绍了在WindowsServer系统上创建一个包含两台成员服务器的故障转移群集,文中通过图文示例介绍的非常详细,对大家的... 目录一、 准备条件二、在ServerB安装故障转移群集三、在ServerC安装故障转移群集,操作与Ser

NFS实现多服务器文件的共享的方法步骤

《NFS实现多服务器文件的共享的方法步骤》NFS允许网络中的计算机之间共享资源,客户端可以透明地读写远端NFS服务器上的文件,本文就来介绍一下NFS实现多服务器文件的共享的方法步骤,感兴趣的可以了解一... 目录一、简介二、部署1、准备1、服务端和客户端:安装nfs-utils2、服务端:创建共享目录3、服

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项目打包部署到服务器的实现

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

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

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

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

Apache Tomcat服务器版本号隐藏的几种方法

《ApacheTomcat服务器版本号隐藏的几种方法》本文主要介绍了ApacheTomcat服务器版本号隐藏的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1. 隐藏HTTP响应头中的Server信息编辑 server.XML 文件2. 修China编程改错误

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

如何在一台服务器上使用docker运行kafka集群

《如何在一台服务器上使用docker运行kafka集群》文章详细介绍了如何在一台服务器上使用Docker运行Kafka集群,包括拉取镜像、创建网络、启动Kafka容器、检查运行状态、编写启动和关闭脚本... 目录1.拉取镜像2.创建集群之间通信的网络3.将zookeeper加入到网络中4.启动kafka集群