重写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虚拟机都会同步的创建一个栈帧用于存储局部变量表&…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
