sylar高性能服务器-日志(P7-P8)代码解析

news2024/10/3 10:34:41

文章目录

    • p7
      • 1.TabFormatItem
      • 2.init函数,对于{}内容的解析
      • 3.Util.h
      • 4.CmakeLists
      • 5.优化日志输出-流式输出
    • p8
      • 1.优化日志输出-格式化输出
      • 2.日志管理器
      • 3.单例模型设计
    • 测试(无调试步骤)

P7P8两节视频新增内容不多,主要看下优化日志输出使用的宏函数。本次记录的内容比较简单,没有一步一步详细写出来,如果对P7之前的代码不存在问题,那么写到这里应该不会存在疑惑。同时建议每一次看代码时都去捋一下日志几大组件之间的关系,看多了真的有点绕。

p7

1.TabFormatItem

新建一个字符标记,优化日志输出格式

class TabFormatItem : public LogFormatter::FormatItem { // 输出tab
public:
    TabFormatItem(const std::string& fmt = "") {}
    void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os << "\t";
    }
};

别忘了去解析字符模板map中进行注册

    static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)>> s_format_items = {
        // {"m",[](const std::string& fmt) { return FormatItem::ptr(new MessageFormatItem(fmt)); } }
#define XX(str,C) \
        {#str, [] (const std::string& fmt) { return FormatItem::ptr(new C(fmt)); }}

        XX(m, MessageFormatItem),           //m:消息
        XX(p, LevelFormatItem),             //p:日志级别
        XX(r, EplaseFormatItem),            //r:累计毫秒数
        XX(c, LoggerNameFormatItem),        //c:日志名称
        XX(t, ThreadIdFormatItem),          //t:线程id
        XX(n, LineFormatItem),              //n:换行
        XX(d, DateTimeFormatItem),          //d:时间
        XX(l, LineFormatItem),              //l:行号
        XX(F, FiberIdFormatItem),           //F:协程id
        XX(T, TabFormatItem),               //T:table
        XX(f, FileNameFormatItem)           //f:文件名
#undef XX

更改日志默认的格式器模板

%d{%Y-%m-%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T%m%n

查看效果

image-20231013085707474

2.init函数,对于{}内容的解析

对格式化模板的解析函数有一点小改动,

  • if(!fmt_status && !isalpha(m_pattern[n]) && m_pattern[n] != '{' && m_pattern[n] != '}')
    

    条件判断加入对当前解析状态fmt_status的判断,防止在{}未解析完跳出。同时把字符标记str的截取放在这里str = m_pattern.substr(i + 1, n - i - 1);,在跳出while后的fmt_status判断条件里,就可以不用再提取。简单改变了一下代码逻辑,自己不更改也没问题

  • if(m_pattern[n] == '}') {
                        fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1);
                        // std::cout << fmt << std::endl;
                        fmt_status = 0;
                        ++ n;
                        break;
                    }
    

    判断}时,结束后fmt_status不再为2,更改为0,也是再跳出while后的fmt_status判断条件里,少写一个判断语句。

  • if (n == m_pattern.size()) {	//最后一个字符, 每次获得str都是走到下一个字符然后进行截取,所以只有最后一个字符需要特殊处理
    				if (str.empty()) {
    					str = m_pattern.substr(i + 1);
    				}
    			}
            }
    

    while循环中,每次可以看作使用双指针的形式,当遍历到最后一个元素时,就会产生越界,从而漏掉最后一个字符的提取。

  • if(fmt_status == 0) {
                if(!nstr.empty()) {
                    vec.push_back(std::make_tuple(nstr, std::string(), 0));
                    nstr.clear();
                }
                vec.push_back(std::make_tuple(str, fmt, 1));
                i = n - 1;
            } else if(fmt_status == 1) {
                std::cout << "pattern parse error: " << m_pattern << " - " << m_pattern.substr(i) << std::endl;
                vec.push_back(std::make_tuple("<parse_error>", fmt, 0));
            } 
    

    这里的判断再经过上面的几个改动后就可以精简一些。

3.Util.h

目前该文件的里的函数有获取线程ID和协程ID。

只实现了一个使用SYS_gettid获取线程ID的函数,不会的可以百度一下这个函数。

// util.h
#ifndef __SYKAR_UTIL_H__
#define __SYKAR_UTIL_H__
#include<pthread.h>
#include<sys/types.h>
#include<sys/syscall.h>
#include <unistd.h>
#include<stdint.h>

namespace sylar {

// 获取线程ID
pid_t GetTreadId();

// 获取协程ID
uint32_t GetFiberId();

}

#endif

// util.cc
#include "util.h"
namespace sylar {

pid_t GetTreadId() { return syscall(SYS_gettid); }

uint32_t GetFiberId() {
    return 0; // TODO
}

}

4.CmakeLists

把util.cc加入cmake配置文件中

set(LIB_SRC
    sylar/log.cc
    sylar/util.cc
    )

5.优化日志输出-流式输出

每次我们在定义日志器的时候比较麻烦,需要去传入一堆参数,而很多参数都是代码会自动获取到的,唯一需要改动的就是传入的日志级别,sylar这里使用了宏函数来使日志的定义更加的方便简洁。

  • 新建一个LogEventWrap类,用来专门存放event事件

    // log.h
    class LogEventWrap {
    public:
        LogEventWrap(LogEvent::ptr e);
        ~LogEventWrap();
        std::stringstream& getSS();
        LogEvent::ptr getEvent() const { return m_event;}
    private:
        LogEvent::ptr m_event;
    };
    // log.cc
    LogEventWrap::LogEventWrap(LogEvent::ptr e)
        : m_event(e)  {
    
    }
    
    LogEventWrap::~LogEventWrap() {
        m_event->getLogger()->log(m_event->getLevel(), m_event); // 把自己写入日志
    }
    
    std::stringstream& LogEventWrap::getSS() {
        return m_event->getSS();
    }
    

    有一个指向event的智能指针成员,一个获得日志打印输出的函数getSS(),和获得event的getEvent函数。

  • #define SYLAR_LOG_LEVEL(logger, level) \
        if(logger->getLevel() <= level) \
            sylar::LogEventWrap(sylar::LogEvent::ptr(new sylar::LogEvent(logger,level, \
                __FILE__,__LINE__,0,sylar::GetTreadId(), \
                sylar::GetFiberId(), time(0)))).getSS()
    
    
    #define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
    #define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)
    #define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)
    #define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)
    #define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)
    

    宏函数里面的代码就是之前我们在test.cc初始化一个日志器使用的方法。

p8

1.优化日志输出-格式化输出

我们在定义日志时,每次传入的格式模板可能不同,在把日志存入文件时,过滤的条件也可能不同,所以sylar继续使用宏函数允许我们自定义传入格式模板

// 格式化输出
#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
    if(logger->getLevel() <= level) \
        sylar::LogEventWrap(sylar::LogEvent::ptr(new sylar::LogEvent(logger, level, \
                        __FILE__, __LINE__, 0, sylar::GetTreadId(),\
                sylar::GetFiberId(), time(0)))).getEvent()->format(fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_DEBUG(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_INFO(logger, fmt, ...)  SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_WARN(logger, fmt, ...)  SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_ERROR(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_FATAL(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, fmt, __VA_ARGS__)

下面详细解析:

  • void format(const char* fmt, ...); // [?]
    void format(const char* fmt, va_list al);
    
    void LogEvent::format(const char* fmt, ...) {
        va_list al;
        va_start(al, fmt);  //引入stdarg.h
        format(fmt, al);
        va_end(al);
    }
    
    
    void LogEvent::format(const char* fmt, va_list al) {
        char* buf = nullptr;
        int len = vasprintf(&buf, fmt, al);
        if(len != -1) {
            m_ss << std::string(buf, len);
            free(buf);
        }
    }
    
    

    在LogEvent中新增两个函数,用于自定义模板

    第一个函数用了可变参数的方法,…的内容可以在输出时用宏定义__VA_ARGS替代。函数体中还使用了va_list,首先定义了一个变量指向va_list,然后使用方法va_start初始化al,使其指向第一个可变参数的地址,接着传入第二个format函数,处理完毕后使用va_end结束可变参数的获取

    image-20231013095837687

    第二个函数传入格式模板fmt,指向可变参数的va_list对象al,创建一个缓冲区buf,使用va_list对像里面的vasprintf方法将格式化数据从可变参数列表写入缓冲区,如果写入的值不为空,则保存到LogEvent的成员m_ss中

2.日志管理器

新增LoggerManager,对日志进行管理

//log.h
// 日志管理器
class LoggerManager {
public:
    LoggerManager();
    Logger::ptr getLogger(const std::string& name);
private:
    std::map<std::string, Logger::ptr> m_loggers;    // 日志器容器
    Logger::ptr m_root;                             // 主日志器
};

// 日志器管理类单例模型
typedef sylar::Singleton<LoggerManager> LoggerMgr;

//log.cc
LoggerManager::LoggerManager() {
    m_root.reset(new Logger);
    m_root->addAppender(LogAppender::ptr(new StdoutAppender));
}

Logger::ptr LoggerManager::getLogger(const std::string& name) {
    auto it = m_loggers.find(name);
    return it == m_loggers.end() ? m_root : it->second;
}

构造函数会初始化一个默认日志器,getLogger会在日志管理器中寻找目标日志

3.单例模型设计

这一块第一次遇到,目前不咋懂,先占个位,后面再记录

#ifndef __SINGLETON_H__
#define __SINGLETON_H__

namespace sylar {

template<class T, class X = void, int N = 0> // T 类型 X 为了创造多个实例对应的Tag N 同一个Tag创造多个实例索引
class Singleton {
public:
    static T* GetInstance() {
        static T v;
        return &v;
    }
};

template<class T, class X = void, int N = 0>
class SingletonPtr {
public:
    static std::shared_ptr<T> GetInstance() {
        static std::shared_ptr<T> v(new T);
        return v;
    }
};

}

#endif

测试(无调试步骤)

贴一下当前代码的测试案例

test.cc

#include <iostream>
#include "../sylar/log.h"
#include "../sylar/util.h"

int main(int argc, char** argv) {
    sylar::Logger::ptr logger(new sylar::Logger);
    logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender)); 
    
    sylar::FileLogAppender::ptr file_appender(new sylar::FileLogAppender("./log.txt"));
    sylar::LogFormatter::ptr fmt(new sylar::LogFormatter("%d%T%m%n"));
    file_appender->setFormatter(fmt);
    file_appender->setLevel(sylar::LogLevel::FATAL); // 过滤特定级别level
    logger->addAppender(file_appender);


    SYLAR_LOG_DEBUG(logger) << "asdasda";
    SYLAR_LOG_FMT_FATAL(logger, "fmt eeror %s", "hy");

    auto l = sylar::LoggerMgr::GetInstance()->getLogger("xx");
    SYLAR_LOG_INFO(l) << "XX";
    return 0;
}

结果

image-20231013102440006

生成的日志文件:

el::FATAL); // 过滤特定级别level
logger->addAppender(file_appender);

SYLAR_LOG_DEBUG(logger) << "asdasda";
SYLAR_LOG_FMT_FATAL(logger, "fmt eeror %s", "hy");

auto l = sylar::LoggerMgr::GetInstance()->getLogger("xx");
SYLAR_LOG_INFO(l) << "XX";
return 0;

}


**结果**

[外链图片转存中...(img-B5hsKY6Q-1697164453590)]

生成的日志文件:

![image-20231013102459920](https://img-blog.csdnimg.cn/img_convert/739a84f3f35b4305e9905fd195a1a68f.png)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python获取网口列表(获取网络接口列表、网口表)socket.if_nameindex()

文章目录 获取网口列表测试 获取网口列表 以下python代码将打印系统中所有存在的网络接口列表&#xff1a; import socketdef print_interfaces_list():# 打印所有的网络接口列表available_interfaces socket.if_nameindex()# 转换成字典形式 {if_index: if_name}available_…

6个视频剪辑必备的素材网站,免费下载。

视频剪辑必备的视频资源、音效素材、BGM&#xff0c;这6个网站全部免费下载&#xff0c;赶紧收藏起来吧&#xff01; 1、菜鸟图库 https://www.sucai999.com/video.html?vNTYxMjky 菜鸟图库网素材非常丰富&#xff0c;网站主要还是以设计类素材为主&#xff0c;高清视频素材…

TSINGSEE青犀智慧广场智能监控解决方案,助力广场监控数字化转型

前期和大家说过世界最大的城市公园——合肥市骆岗公园的监控方案&#xff0c;大家都很感兴趣&#xff0c;后台还有粉丝留言想看看广场类场景的智能方案&#xff0c;今天小编就和大家聊一聊。 广场视频监控方案大体和公园场景类似&#xff0c;但由于广场比公园更加空旷&#xf…

centos 里面的service自启动app.jar,出现两个java进程,app是同一个端口

当使用jps -lv查看java虚拟机进程 app.jar启动后&#xff0c;居然出现两个启动进程&#xff0c;而且他们的端口都一样&#xff0c;同一端口&#xff0c;是不允许启动两个相同app的。 使用进程ps查看进程工具 #ps -aux 参数说明&#xff1a; a: 显示跟当前终端关联的所有进…

到2026年,超过80%企业将使用生成式AI

10月12日&#xff0c;全球著名信息咨询调查机构Gartner在官网&#xff0c;公布了一项调查数据&#xff0c;到2026年&#xff0c;超过80%的企业将使用生成式AI API&#xff0c;或部署生成式AI的应用程序。而2023年这一比例还不到5%。 Gartner副总裁兼高级分析师 Arun Chandrase…

【约束布局】ConstraintLayout配合Guideline解决两个子控件其中一个被挤出屏幕的问题

一、需求 屏幕横向显示文本框A和图标B&#xff0c;A在B的左侧&#xff0c;B紧贴在A的右边显示&#xff0c;文本框A的字数不确定&#xff0c;文本框A的字数足够多时&#xff0c;换行显示&#xff0c;并且保证图标B一直在文本框A的右侧&#xff0c;且不被挤出屏幕。 二、问题 本来…

Java Object转String方式

Map<String,Object> map new HashMap<>(); map.put("a1","a"); map.put("a2",""); map.put("a3",1); map.put("a4",null);一、强制转换 value "a"或""可以进行强制转换String…

众佰诚:新手开抖音小店申请流程是什么

抖音小店为抖音平台上的商家提供了一个全新的销售渠道&#xff0c;让更多创业者能够轻松实现线上销售。如果你是一位希望在抖音上开展电商业务的新手&#xff0c;下面将为你详细介绍如何申请开通抖音小店。 一、准备工作 首先&#xff0c;你需要准备好以下材料&#xff1a; 营业…

数学术语之源——代数——(子空间的)直和(direct sum)

1. 关于(子空间的)直和(direct sum)的较正式定义 令 为向量空间 的子空间,若 且 是独立的&#xff0c;则称 是子空间 的直和(direct sum), 记为 &#xff0c; 这种表示在同一个基的前提下是唯一的。 一个直观几何类比理解(个人愚见)&#xff1a;如果我将向量空间V 看…

计算机基础——内存

文章目录 内存一、内存条、总线、DMA二、内存管理1、为什么要有逻辑地址2、逻辑地址和物理地址如何映射3、分页时间和空间优化4、程序内部的内存管理-分段 三、内存相关的系统调用1、用户态和内核态 四、Java内存 内存 提示&#xff1a;这里可以添加本文要记录的大概内容&…

JS+Jquery用法

1. 当存在多个select时&#xff0c;想要获取每一个select的选中的值(使用变量赋值的方法). var Metric "";$(#Metric).change(function () {Metric $(this).children("option:selected").val();console.log("Metric:" Metric);}); 2. 在页面…

海外代理IP与VPN有何区别?哪个更好?

当谈到网络安全和IP变更时&#xff0c;人们会想到VPN和IP代理服务器。很多人很困惑&#xff0c;它们之间有什么区别&#xff0c;应该选择哪一个呢&#xff1f;这取决于您的需求来决定哪一个更好。 一、什么是VPN与IP代理&#xff1f; VPN 是虚拟专用网络 (Virtual Private Net…

ACP.项目管理.5种复盘会议

复盘要怎么做的有水准&#xff0c;让领导满意&#xff0c;方式方法很重要。今天给你们安利5种复盘方法&#xff0c;保准你省事&#xff0c;领导还满意。 一、KPT复盘法 7月份年中一直在做和复盘相关的事&#xff0c;像公司的OKR复盘、年中战略规划&#xff0c;不过日常很多生…

Hadoop 安装教程 (Mac m1/m2版)

安装JDK1.8 这里最好是安装1.8版本的jdk 1. 进入官网Java Downloads | Oracle Hong Kong SAR, PRC,下滑到中间区域找到JDK8 2.选择mac os,下载ARM64 DMG Installer对应版本 注&#xff1a;这里下载需要注册oracle账号&#xff0c;不过很简单&#xff0c;只需要提供邮箱即可&…

【C++】模板进阶 -- 详解

一、非类型模板参数 模板参数 分类类型形参与非类型形参。 类型形参&#xff0c;即出现在模板参数列表中&#xff0c;跟在 class 或者 typename 之类的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类&#xff08;函数&#xff09;模板的一个参数&#xff0c;在类…

基于SSM的毕业生就业管理平台设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

2023年,考PMP用处大吗?

就本身PMP的价值而言&#xff0c;不管到多少年&#xff0c;跟新迭代下&#xff0c;用处都是很大的&#xff0c;就看你会不会用。 PMP会让你学到一套系统的项目管理的流程&#xff0c;还有作为项目管理人士该具备的素质和技能&#xff0c;这就是使得&#xff0c;即便从未接触过…

谷粒商城笔记+踩坑(25)——整合Sentinel实现流控和熔断降级

导航&#xff1a; 【Java笔记踩坑汇总】Java基础进阶JavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线MySQL高级篇设计模式常见面试题源码 SpringCloud基础5——微服务保护、Sentinel 目录 一、Sentinel概述 1.1、服务流控、熔断和降级 1.2、Sentinel 简介…

能否翻译翻译,到底什么才叫“精通Java” ?

01 模糊的岗位能力标准 技术类人员的招聘始终是令HR 与技术面试官头疼的事。 在一般招聘流程中&#xff0c;当确定了某个岗位招聘需求后&#xff0c;技术面试官会与HR 一同商讨并明确该岗位的画像。 明确画像后&#xff0c;一般HR 会负责在招聘平台书写岗位JD&#xff0c;技…

不同商家的订单详情API接口可能会有不同的实现方式,下面是一个通用的订单详情API接口的示

不同商家的订单详情API接口可能会有不同的实现方式&#xff0c;下面是一个通用的订单详情API接口的示例&#xff1a; 请求方式&#xff1a;使用HTTP或HTTPS协议&#xff0c;向指定URL发送GET请求&#xff0c;获取订单详情。 URL格式&#xff1a;商家订单详情API的URL通常由两部…