重写Sylar基于协程的服务器(1、日志模块的架构)
重写Sylar基于协程的服务器(1、日志模块的架构)
重写Sylar基于协程的服务器系列:
重写Sylar基于协程的服务器(0、搭建开发环境以及项目框架 || 下载编译简化版Sylar)
重写Sylar基于协程的服务器(1、日志模块的架构)
重写Sylar基于协程的服务器(2、配置模块的设计)
重写Sylar基于协程的服务器(3、协程模块的设计)
前言
和Muduo的日志对比,Muduo的同步日志虽然格式固定,但简单高性能。而sylar的日志设计的显得过于冗余,虽然灵活性强、扩展性高,但是性能却不及Muduo。尽管陈硕大大也说过,简单才能保证高性能,日志就没必要设计的那么花里胡哨,但是sylar对日志的设计思想以及设计模式超级超级适合小白去学习。
日志格式
由于用户可能并不需要将日志上下文的每一项都进行输出,而是希望可以自由地选择要输出的日志项。并且,用户还可能需要在每条日志里增加一些指定的字符,比如在文件名和行号之间加上一个冒号的情况。为了实现这项功能,LogFormatter使用了一个模板字符串来指定格式化的方式。模板字符串由普通字符和占位字符构成。在构造LogFormatter对象时会指定一串模板字符,LogFormatter会首先解析该模板字符串,将其中的占位符和普通字符解析出来。在格式化日志上下文时,根据模板字符串,将其中的占位符替换成日志上下文的具体内容,普通字符保持不变。下表是支持的占位符的含义。
占位符 | 含义 |
---|---|
%s | 普通字符串(直接输出的字符串) |
%d | 时间 |
%t | 线程真实id |
%N | 线程名 |
%f | 协程id |
%p | 日志级别 |
%c | 日志器名 |
%F | 文件路径 |
%l | 行号 |
%m | 日志消息 |
%T | Tab缩进 |
%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实现的日志中,一条日志数据流向是:日志包装器->日志器->数个日志输出地,如图所示。
关于这几个类的设计如下:
-
LogEvent: 日志上下文,用于记录日志现场,比如该日志的时间,文件路径/行号,日志级别,线程/协程号,日志消息等。
-
LogEventWrap: 日志事件包装类,其实就是将日志事件和日志器一起包装到日志上下文中,因为一条日志只会在一个日志器上进行输出。将日志事件和日志器包装到一起后,方便通过宏定义来简化日志模块的使用(这点和Muduo很像)。另外,LogEventWrap还负责在构建时指定日志事件和日志器,在析构时调用日志器的log方法将日志事件进行输出。
-
Logger: 日志器,负责进行日志输出。一个Logger包含多个LogAppender和一个日志级别,提供log方法,传入日志事件,如果日志事件的级别高于日志器本身的级别就调用每一个LogAppender的log方法将日志进行输出,否则该日志被抛弃。
日志包装器以及日志器伪代码:
-
LogAppender: 日志输出器,用于将一个日志上下文输出到对应的输出地。该类内部包含一个LogFormatter成员和一个log方法,日志事件先经过LogFormatter格式化后再输出到对应的输出地。从这个类可以派生出不同的输出器类型,比如StdoutLogAppender和FileLogAppender,分别表示终端和文件的日志输出器。
日志输出器伪代码:
-
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++;}// ... 省略不重要的部分}
-
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、日志模块的架构)
重写Sylar基于协程的服务器(1、日志模块的架构) 重写Sylar基于协程的服务器系列: 重写Sylar基于协程的服务器(0、搭建开发环境以及项目框架 || 下载编译简化版Sylar) 重写Sylar基于协程的服务器(1、日志模…...

ElementUI Form:Radio 单选框
ElementUI安装与使用指南 Radio 单选框 点击下载learnelementuispringboot项目源码 效果图 el-radio.vue (Radio 单选框)页面效果图 项目里el-radio.vue代码 <script> export default {name: el_radio,data() {return {radio: 1,radio2: 2,ra…...
react-activation实现缓存,且部分页面刷新缓存,清除缓存
1.安装依赖 npm i -S react-activation2.使用AliveScope 包裹根组件 import { AliveScope } from "react-activation" <AliveScope><Router><Switch><Route exact path"/" render{() > <Redirect to"/login" push …...

idea 中 tomcat 乱码问题修复
之前是修改 Tomcat 目录下 conf/logging.properties 的配置,将 UTF-8 修改为 GBK,现在发现不用这样修改了。只需要修改 IDEA 中 Tomcat 的配置就可以了。 修改IDEA中Tomcat的配置:添加-Dfile.encodingUTF-8 本文结束...

Modbus协议学习第七篇之libmodbus库API介绍(modbus_write_bits等)
写在前面 在第六篇中我们介绍了基于libmodbus库的演示代码,那本篇博客就详细介绍一下第六篇的代码中使用的基于该库的API函数。另各位读者,Modbus相关知识受众较少,如果觉得我的专栏文章有帮助,请一定点个赞,在此跪谢&…...

第九节HarmonyOS 常用基础组件13-TimePicker
1、描述 时间选择组件,根据指定参数创建选择器,支持选择小时以及分钟。默认以24小时的时间区间创建滑动选择器。 2、接口 TimePicker(options?: {selected?: Date}) 3、参数 selected - Date - 设置选中项的时间。默认是系统当前的时间。 4、属性…...
力扣刷题-55.跳跃游戏
给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。 class Solution { publ…...
Ruby安装演示教程
安装 Ruby 的过程会根据您的操作系统(如 Windows、MacOS、Linux)而有所不同。以下是在这些主要平台上安装 Ruby 的基本指南。 在 Windows 上安装 Ruby 下载 Ruby Installer:访问 RubyInstaller 官方网站下载适合您系统的 Ruby Installer 版…...
前端使用vue-simple-uploader进行分片上传
目录 一、安装vue-simple-uploader 二、在vue中使用 一、安装vue-simple-uploader npm install vue-simple-uploader --save main.js初始化vue-simple-uploader import uploader from vue-simple-uploaderVue.use(uploader) common/config文件 export const ACCEPT_CONF…...
Java 源代码中常见的数据类型
在Java源代码中,常见的数据类型包括基本数据类型(Primitive Data Types)和引用数据类型(Reference Data Types)。这些数据类型在Java中用于存储不同种类的数据,如整数、小数、字符、布尔值以及对象等。 1.…...

Web3行业研究逐步加强,“链上数据”缘何成为关注焦点?
据中国电子报报道,近日,由中关村区块链产业联盟指导,中国信息通信研究院牵头,欧科云链控股有限公司参与编写的《全球Web3产业全景与发展趋势研究报告(2023年)》正式发布。研究报告通过全面追踪国内外Web3产…...

逸学区块链【solidity】真随机数
参考Get a Random Number | Chainlink Documentation 但是很贵,价格 Gas Price:当前gas价格,根据网络状况而波动。Callback gas :返回您所请求的随机值时,回调请求消耗的gas 量。验证gas :量gas 用于验证…...
【WPF.NET开发】优化性能:对象行为
本文内容 不删除对象的事件处理程序可能会使对象保持活动状态依赖属性和对象Freezable 对象用户界面虚拟化 了解 WPF 对象的内部行为有助于在功能和性能之间做出适当的取舍。 1、不删除对象的事件处理程序可能会使对象保持活动状态 对象传递给其事件的委托是对该对象的有效…...
uniapp中封装一个svg转base64的组件
uniapp中由于不支持svg--》base64,同时无法使用h5中atob,这里我们采用js-base64插件实现这样一个组件,只要传人svg的代码即可在uniapp中转为base64,同时支持自定义参数,比如宽度,高度,等 1 安装 npm inst…...

QT播放gstreamer命令(三)---使用QMediaPlayer
前文: 因为之前听说过,QMediaPlayer已经集成了gstreamer,但是并没有什么接口来例子来说明,根本看不出来有任何gstreamer的形式,于是在QT5助手里面搜了一下,发现确实有gstreamer的痕迹,但是例子写…...
Ubuntu22扩大分区
一台Ubuntu一直以为扩展成功了的,但是用起来空间不够,才发现空间还是那么小,所以赶快想办法扩展。 首先尝试使用gparted软件,结果在软件里面发现硬盘分区/dev/sda3已经全分配78G了。 但是看df -H,明明没有扩展: /dev…...

数据结构篇-05:哈希表解决字母异位词分组
本文对应力扣高频100 ——49、字母异位词分组 哈希表最大的特点就是它可以把搜索元素的时间复杂度降到O(1)。这一题就是要我们找到 “字母异位词” 并把它们放在一起。 “字母异位词”就是同一个单词中字母的不同组合形式。判断“字母异位词”有两个视角:1、所含字…...

添加了gateway之后远程调用失败
前端提示500,后端提示[400 ] during [GET] to [http://userservice/user/1] 原因是这个,因为在请求地址写了两个参数,实际上只传了一个参数 解决方案:加上(required false)并重启所有相关服务...

C#,哥伦布数(Golomb Number)的算法与源代码
1 哥伦布数(Golomb Number) 哥伦布数(Golomb Number)是一个自然数的非减量序列,使得n在序列中正好出现G(n)次。前几个15的G(n)值为:1 2 2 3 3 4 4 4 5 5 5 6…...

JVM学习
1.Java虚拟机内部有哪些线程共享,那些线程隔离 程序计数器: 通过改变这个计数器的值来选取下一条需要执行的字节码命令 Java虚拟机栈: 栈,每个方法被执行时,Java虚拟机都会同步的创建一个栈帧用于存储局部变量表&…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤: 第一步: 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为: // 改为 v…...
深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙
WebGL:在浏览器中解锁3D世界的魔法钥匙 引言:网页的边界正在消失 在数字化浪潮的推动下,网页早已不再是静态信息的展示窗口。如今,我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室,甚至沉浸式的V…...

大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...