重写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


本章完结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/359362.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣hot100 组合总和 回溯 剪枝 组合

Problem: 39. 组合总和 文章目录 思路复杂度&#x1f496; Code 思路 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) &#x1f496; Code class Solution{List<List<Integer>> res new ArrayList<>();int x;// 全局targetin…

18.通过telepresence调试部署在Kubernetes上的微服务

Telepresence简介 在微服务架构中,本地开发和调试往往是一项具有挑战性的任务。Telepresence 是一种强大的工具,使得开发者本地机器上开发微服务时能够与运行在 Kubernetes 集群中的其他服务无缝交互。本文将深入探讨 Telepresence 的架构、运行原理,并通过实际的案例演示其…

【Golang】ModbusRTU协议CRC16校验算法

CRC校验码是通过在数据后面附加一个短的校验序列来生成的&#xff0c;用于检测数据在传输过程中是否发生错误。CRC16是一种特定的CRC校验算法&#xff0c;它生成一个16位的校验码。 下面是使用Go语言实现CRC16校验算法的代码&#xff1a; package main import ("encoding…

ASP.NET Core 使用 SignalR 的简单示例

写在前面 ASP.NET SignalR 是一个开源代码库&#xff0c;简化了Web实时通讯方案&#xff0c;可以实时地通过服务端将信息同步推送到各个客户端&#xff0c;可应用于 需要从服务器进行高频更新的应用&#xff1a;包括游戏、社交网络、投票、拍卖、地图和GPS应用&#xff1b; 仪…

汽车燃油泵数据分析:全球市场的年复合增长率将达到10%左右

燃油泵是汽车配件行业的专业术语。是电喷汽车燃油喷射系统的基本组成之一&#xff0c;位于车辆油箱内部&#xff0c;燃油泵在启动和发动机运转时工作&#xff0c;如果发动机停止而点火开关仍处于ON时&#xff0c;HFM-SFI控制模块关闭燃油泵的电源&#xff0c;以避免意外点火。 …

【Vue.js设计与实现】第一篇:框架设计概览-阅读笔记(持续更新)

从高层设计的角度去探讨框架需要关注的问题。 参考&#xff1a;速读《Vue.js 设计与实现》 - 掘金 (juejin.cn) 系列目录&#xff1a; 标题博客第一篇&#xff1a;框架设计概览【Vue.js设计与实现】第一篇&#xff1a;框架设计概览-阅读笔记第二篇&#xff1a;响应系统【Vue.…

远程教育:低代码在教育技术领域的重塑之力

新冠肺炎大流行对世界各地的行业产生了影响&#xff0c;其中一些行业的影响远远超过其他行业。食品、零售、供应链、娱乐和航空业是受影响最大的行业&#xff0c;为确保不间断运营&#xff0c;这引发了一场数字革命。相信&#xff0c;这种数字化的采用将长期保持下去&#xff0…

如何使用 Google 搜索引擎保姆级教程(附链接)

一、介绍 "Google语法"通常是指在 Google 搜索引擎中使用一系列特定的搜索语法和操作符来精确地定义搜索查询。这些语法和操作符允许用户过滤和调整搜索结果&#xff0c;提高搜索的准确性。 二、安装 Google 下载 Google 浏览器 Google 官网https://www.google.c…

Android 使用高德地图

一、获取高德平台key 【1】基于application包名&sha1值在高德控制台获取key值&#xff0c;详情参考&#xff1a; 获取Key-创建工程-开发指南-Android 地图SDK | 高德地图API 【2】在manifest中声明权限 【3】将拿到的key值在manifest中进行声明 <!--允许程序打开网络…

沈阳地铁四号线部署智和信通运维方案,实现对工业交换机统一纳管

沈阳地铁4号线一期工程线路起于正新路站&#xff0c;途经和平区、沈河区、大东区、苏家屯区、浑南区&#xff0c;贯穿沈阳北站、沈阳南站、太原街、沈阳大学、长白岛、浑南产业区等区域&#xff0c;止于创新路站&#xff0c;大致呈南北走向。 项目现状 沈阳地铁4号线一期工程线…

点餐APP开发需要用到的一些源代码

在数字化日益普及的今天&#xff0c;点餐APP已经成为人们日常生活的重要组成部分&#xff0c;从校园食堂到高级餐厅&#xff0c;从快餐外卖到定制化营养餐&#xff0c;点餐APP的多样性和便利性满足了不同用户的需求&#xff0c;本文将深入探讨在开发一款点餐APP时可能用到的源代…

Android Studio 出现 “Index is not created for `Stubs‘“ 的原因,如何解决?

亲爱的朋友们&#xff01;大家好我是咕噜铁蛋&#xff01;&#xff0c;今天我将与大家分享一个在Android Studio开发过程中可能遇到的问题&#xff1a;“Index is not created for Stubs”。这个问题看似不起眼&#xff0c;但实际上可能会对开发效率产生不小的影响。下面&#…

c++之说_9_6|自定义类型 struct operator 重载运算符关键字

c中 struct 和 class 特性几乎一模一样 所以很多都是共有的特性 篇幅就很多了 c中 我觉得最牛皮的概念之一 就是 重载运算符 operator 关键字 这个东西 能将 我们的 运算符 &#xff08;-*/&#xff09; new delete 类型转换 写在类里面当成员函数 并制定 该类型变…

大数据-Spark-关于Json数据格式的数据的处理与练习

上一篇&#xff1a; 大数据-MapReduce-关于Json数据格式的数据的处理与练习-CSDN博客 16.7 Json在Spark中的引用 依旧利用上篇的数据去获取每部电影的平均分 {"mid":1,"rate":6,"uid":"u001","ts":15632433243} {"m…

linux系统查看占用cpu程序

目录 一&#xff1a;top 二&#xff1a; ps 三&#xff1a;perf 四&#xff1a;/proc/stat 五&#xff1a;pidstat 一&#xff1a;top 使用 top 命令&#xff1a;在终端中输入 top 命令&#xff0c;系统会显示当前正在运行的进程和它们的资源占用情况。默认情况下&#…

使用AKStream对接gb28181

优点&#xff1a;功能比较多&#xff0c;C#开发的&#xff0c;容易修改&#xff0c;内嵌入了zlmk流媒体服务品&#xff0c;启动简单 缺点&#xff1a;sip对摄像头兼容还有问题&#xff0c;大华接入非常不稳定&#xff0c;注册等待时间久&#xff0c;对海康是正常&#xff0c;占…

Oracle 的闪回技术是什么

什么是闪回 Oracle 数据库闪回技术是一组独特而丰富的数据恢复解决方案&#xff0c;能够有选择性地高效撤销一个错误的影响&#xff0c;从人为错误中恢复。闪回是一种数据恢复技术&#xff0c;它使得数据库可以回到过去的某个状态&#xff0c;可以满足用户的逻辑错误的快速恢复…

LabVIEW电液伺服控制系统

介绍了如何利用ARM微处理器和LabVIEW软件开发一个高效、精准的电液伺服控制系统。通过结合这两种技术&#xff0c;我们能够提高系统的数字化程度、集成化水平&#xff0c;以及控制精度&#xff0c;从而应对传统电液伺服控制器面临的问题。 该电液伺服控制系统由多个关键部分组…

TypeScript(八) number和string

1. TypeScript number 1.1. 描述 Number对象是原始数值的包装对象。 1.2.语法 var num new Number(value);;注意&#xff1a;如果一个参数值不能转换为一个数字&#xff0c;将返回NaN&#xff08;非数字值&#xff09;。 1.3. 对象属性 属性描述MAX_VALUE可表示的最大的数…

Postman-接口测试教程

接口是软件开发中常用的概念&#xff0c;是软件生产过程中比较核心的任务。对于接口开发者&#xff0c;调试接口是一件较为繁琐的事情&#xff0c;很多时候需要线上线下来回切换。在这里&#xff0c;我就跟大家介绍一个只需要在本地就可以调试接口的方法&#xff0c;即使用post…