sylar源码阅读笔记-日志系统-日志初始化init()方法解析

本文对sylar日志系统的日志初始化init方法进行解析,记录个人学习过程中的理解,不对的地方请多多指正。
新建一个日志类Logger的对象时,需要对其进行初始化,调用构造函数,如下:

// 构造函数
Logger::Logger(const std::string &name): m_name(name), m_level(LogLevel::DEBUG)
{
    //将Logger中成员变量m_formatter进行初始化,指定了默认的日志格式(后面这一长串)
    m_formatter.reset(new LogFormatter("%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"));
}

经过上边这个操作,我们的日志类Logger,它可以按照指定格式输出日志了,每个字符代表含义具体如下:

/**
*  %m 消息
*  %p 日志级别
*  %r 累计毫秒数
*  %c 日志名称
*  %t 线程id
*  %n 换行
*  %d 时间
*  %f 文件名
*  %l 行号
*  %T 制表符
*  %F 协程id
*  %N 线程名称
*/

例如指定上边格式后,日志输出结果为:

2023-11-07 10:06:00     2048    thread_name      1024    [INFO]  [logger]   /root/c_plus_plus_project/sylar/tests/test_log.cc:40    test info
// 时间					线程id	线程名称			协程id	[日志级别]	[日志名称]		文件名:行号:										消息 换行符号

那么具体如何从给定格式然后进行解析输出上边格式的日志呢?

核心就在于初始化方法,解析日志格式:

void LogFormatter::init();

我们先假设函数中字符串m_pattern值如下:

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

我们先抛开代码不看,先看看如果让我们自己手动解析,应该如何解析?(先想清楚逻辑,再看代码,会比较清楚)

首先应该将m_pattern进行拆分,拆分成各个单项,然后将每个单项交给对应的解析类,调用对应解析类的fotmat方法,将解析结果存入到输出流中,这样所有单项解析完成后,输出流中就保存了我们想输出的日志。

m_pattern拆分结果如下:

m_pattern="%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 说明:%d表示格式为时间,后面的大括号{}里面存放的是解析时间的格式,具体交给解析时间的类来处理。
%T
%t
%T
%N
%T
%F
%T
[
%p
]
%T
[
%c
]
%T
%f
:
%l
%T
%m
%n
*

这时候可能很多同学就有疑问了。

问:为什么大括号{}内的字符"%Y-%m-%d %H:%M:%S"没有被拆分?

答:前面%d表示格式为时间,后面的大括号{}里面存放的是解析时间的格式,具体交给解析时间的类来处理。函数LogFormatter::init()只需要找出大括号的起始位置’{‘和结束位置’}',然后把中间的内容%Y-%m-%d %H:%M:%S取出来,交给对应的解析类即可,例如时间,只需要将%Y-%m-%d %H:%M:%S交给时间解析类DateTimeFormatItem,告诉该如何解析时间**。

问:为什么字符 ‘[’,‘]’,‘:’ 被解析成了单行了,而不是写成[%p],[%c],%f: ?

答:我感觉日志解析类要求格式都应该以百分号’%‘开头,然后把其他字符 ‘[’,’]‘,’:’ ,都当作字符串来处理,对应解析类为StringFormatItem,然后直接输出’[‘,’]‘,’:’ 本身对应的值,(后面还会提到如何处理的)。

OK,现在我们似乎更够看出来一个套路,我们需要将m_pattern拆分成各个单项,然后找到单项对应的格式,然后给这个单项指定解析类

可以写成如下的形式:

/*
<符号,格式,解析类>
举例:
<"p","",LevelFormatItem>
<"c","",NameFormatItem>
<"n","",NewLineFormatItem>
<"d","%Y-%m-%d %H:%M:%S",DateTimeFormatItem>
*/

另外,需要注意的是,本函数中新增了一位类型,来表示该格式是已经定义好的格式(例如%p,%c,…),还是普通字符格式(‘[’,‘]’,‘:’),普通字符格式需要使用StringFormatItem来进行处理。

上边的形式可改写如下:

/*
type为0,表示为普通字符,使用StringFormatItem来处理
type为1,表示为一定定义好的,使用对应的xxxFormatItem来处理
四元组
<符号,格式,解析类,type>
举例:
<"p","",LevelFormatItem,1>
<"c","",NameFormatItem,1>
<"n","",NewLineFormatItem,1>
<"d","%Y-%m-%d %H:%M:%S",DateTimeFormatItem,1>

<"[",StringFormatItem,0>
<"]",StringFormatItem,0>
<":",StringFormatItem,0>
*/

具体的符号和解析类的对应关系我放到了下面:

/**
*	符号		代表含义	解析类						解析格式						
*	%m 		消息		    MessageFormatItem			-		
*	%p 		日志级别	        LevelFormatItem			   -	
*	%r 		累计毫秒数	  ElapseFormatItem			  -
*	%c 		日志名称	      NameFormatItem			   -
*	%t 		线程id	    ThreadIdFormatItem			-
*	%n 		换行		    NewLineFormatItem		    -
*	%d 		时间		   DateTimeFormatItem		%Y-%m-%d %H:%M:%S
*	%f 		文件名	       FilenameFormatItem		    -
*	%l 		行号			LineFormatItem				-
*	%T 		制表符		   TabFormatItem				-
*	%F 		协程id		FiberIdFormatItem			-
*	%N 		线程名称	   ThreadNameFormatItem			-
*	x		其他字符	   StringFormatItem				-				格式串m_pattern中其他字符,例如[]:,
*/
// 每个解析类xxxFormatItem都包含format方法,用于自定义格式化方法,例如MessageFormatItem就是将日志消息存放到输出流中
// 消息Format
class MessageFormatItem : public LogFormatter::FormatItem
{
public:
    MessageFormatItem(const std::string &fmt){}
    void format(std::ostream &os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override
    {os << event->getContent();}
};

到这里,我们应该能够明白该函数大体上的逻辑,自己按照这个逻辑顺一下代码,即可理解。

下面介绍几个我在学习过程中,疑问的点,可能方便你的理解。

问:函数中各变量的含义是什么?

答:

std::vector<std::tuple<std::string, std::string, int>> vec;
/*
定义一个vector,其中每个元素是一个三元组<std::string, std::string, int>
打住,刚刚前面不是说四元组吗?<符号,格式,解析类,type>
本函数中将“符号”和“解析类”的映射关系存到了map,在代码中可以看到
所以就简化为了三元组<符号,格式,type>
*/

std::string nstr;
// 存放格式化字符 m p r ...
语法函数知识点记录
std::stringstream

字符串流,需要引用头文件,像管理字符串一样,可以将输出先存到ss中,然后调用str()方法获取。

stringstream s;
s << "hello"
    << " "
    << "world"; // 将多个字符串放入字符串流中
cout << s.str() << endl;
s.str(""); // 清空字符串流
cout << s.str() << endl;
return 0;
std::function

std::function是一个函数包装器,该函数包装器模板能包装任何类型的可调用实体。

// std::function<void(int)> f; 表示function的对应f参数是int,返回值是void
#include <functional>
#include <iostream>

struct Foo
{
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_ + i << '\n'; }
    int num_;
};

void print_num(int i) { std::cout << i << '\n'; }

int main()
{
    // 存储自由函数
    std::function<void(int)> f_print_num = print_num;
    f_print_num(5); // 5

    // 存储lambda
    std::function<void()> f_print_42 = []()
    {
        print_num(42);
    };
    f_print_42(); // 42

    // 存储成员函数
    std::function<void(const Foo &, int)> foo_print_add = &Foo::print_add;
    const Foo foo(10);
    foo_print_add(foo, 10); // 20
    foo_print_add(foo, 20); // 30

    return 0;
}

源码解析:

// map<字符串,FotmatItem的构造函数(需要传入参数str)>
// 这里使用的而是lambda表达式
std::function<FormatItem::ptr(const std::string &str) p=[](const std::string &fmt){
    return FormatItem::ptr(new C(fmt));
}
static std::map<std::string, std::function<FormatItem::ptr(const std::string &str)>> s_format_items = {
#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, ElapseFormatItem),     // r:累计毫秒数
            XX(c, NameFormatItem),       // c:日志名称
            XX(t, ThreadIdFormatItem),   // t:线程id
            XX(n, NewLineFormatItem),    // n:换行
            XX(d, DateTimeFormatItem),   // d:时间
            XX(f, FilenameFormatItem),   // f:文件名
            XX(l, LineFormatItem),       // l:行号
            XX(T, TabFormatItem),        // T:Tab
            XX(F, FiberIdFormatItem),    // F:协程id
            XX(N, ThreadNameFormatItem), // N:线程名称
#undef XX
        };

代码注释:

void LogFormatter::init()
 {
     // 解析核心代码
     // m_pattern: %d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n
     // 三元组<str, format, type>
     // type==0  StringFormatItem    用于存放m_pattern的普通字符,如'[',']',':'
     // type==1  其他的FormatItem
     // str      m p r ...
     std::vector<std::tuple<std::string, std::string, int>> vec;
     // 存放格式化字符 Y m d etc...
     std::string nstr;
     for (size_t i = 0; i < m_pattern.size(); ++i)
     {
         if (m_pattern[i] != '%')
         {
             // 如果不是%号
             // nstr字符串后添加1个字符m_pattern[i]
             nstr.append(1, m_pattern[i]);
             continue;
         }

         if ((i + 1) < m_pattern.size())
         {
             // m_pattern[i]是% && m_pattern[i + 1] == '%' ==> 两个%,第二个%当作普通字符
             if (m_pattern[i + 1] == '%')
             {
                 nstr.append(1, '%');
                 continue;
             }
         }

         // m_pattern[i]是% && m_pattern[i + 1] != '%', 需要进行解析
         size_t n = i + 1;     // 跳过'%',从'%'的下一个字符开始解析
         int fmt_status = 0;   // 是否解析大括号内的内容: 已经遇到'{',但是还没有遇到'}' 值为1
         size_t fmt_begin = 0; // 大括号开始的位置

         std::string str;
         std::string fmt; // 存放'{}'中间截取的字符
         // 从m_pattern[i+1]开始遍历
         while (n < m_pattern.size())
         {
             // m_pattern[n]不是字母 & m_pattern[n]不是'{' & m_pattern[n]不是'}'
             if (!fmt_status && (!isalpha(m_pattern[n]) && m_pattern[n] != '{' && m_pattern[n] != '}'))
             {
                 str = m_pattern.substr(i + 1, n - i - 1);
                 break;
             }
             if (fmt_status == 0)
             {
                 if (m_pattern[n] == '{')
                 {
                     // 遇到'{',将前面的字符截取
                     str = m_pattern.substr(i + 1, n - i - 1);
                     // std::cout << "*" << str << std::endl;
                     fmt_status = 1; // 标志进入'{'
                     fmt_begin = n;  // 标志进入'{'的位置
                     ++n;
                     continue;
                 }
             }
             else if (fmt_status == 1)
             {
                 if (m_pattern[n] == '}')
                 {
                     // 遇到'}',将和'{'之间的字符截存入fmt
                     fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1);
                     // std::cout << "#" << fmt << std::endl;
                     fmt_status = 0;
                     ++n;
                     // 找完一组大括号就退出循环
                     break;
                 }
             }
             ++n;
             // 判断是否遍历结束
             if (n == m_pattern.size())
             {
                 if (str.empty())
                 {
                     str = m_pattern.substr(i + 1);
                 }
             }
         }

         if (fmt_status == 0)
         {
             if (!nstr.empty())
             {
                 // 保存其他字符 '['  ']'  ':'
                 vec.push_back(std::make_tuple(nstr, std::string(), 0));
                 nstr.clear();
             }
             // fmt:寻找到的格式
             vec.push_back(std::make_tuple(str, fmt, 1));
             // 调整i的位置继续向后遍历
             i = n - 1;
         }
         else if (fmt_status == 1)
         {
             // 没有找到与'{'相对应的'}' 所以解析报错,格式错误
             std::cout << "pattern parse error: " << m_pattern << " - " << m_pattern.substr(i) << std::endl;
             m_error = true;
             vec.push_back(std::make_tuple("<<pattern_error>>", fmt, 0));
         }
     }

     if (!nstr.empty())
     {
         vec.push_back(std::make_tuple(nstr, "", 0));
     }
     /**
      * s_format_items<格式标识:string,对应的对象:FormatItem>
      * 不同的FormatItem可以实现自己的format方法进行日志格式化
      * new 一个FormatItem对象,传入格式为fmt
      */
     static std::map<std::string, std::function<FormatItem::ptr(const std::string &str)>> s_format_items = {
#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, ElapseFormatItem),     // r:累计毫秒数
         XX(c, NameFormatItem),       // c:日志名称
         XX(t, ThreadIdFormatItem),   // t:线程id
         XX(n, NewLineFormatItem),    // n:换行
         XX(d, DateTimeFormatItem),   // d:时间
         XX(f, FilenameFormatItem),   // f:文件名
         XX(l, LineFormatItem),       // l:行号
         XX(T, TabFormatItem),        // T:Tab
         XX(F, FiberIdFormatItem),    // F:协程id
         XX(N, ThreadNameFormatItem), // N:线程名称
#undef XX
     };

     for (auto &i : vec)
     {
         // 三元组<str, format, type>
         if (std::get<2>(i) == 0)
         {
             // 存放需要解析的单项,这个为普通字符
             m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(i))));
         }
         else
         {
             // 从s_format_items寻找,返回对应的对象
             auto it = s_format_items.find(std::get<0>(i)); // 获得i的第1项
             if (it == s_format_items.end())
             {
                 // 格式未从s_format_items中找到
                 m_items.push_back(FormatItem::ptr(new StringFormatItem("<<error_format %" + std::get<0>(i) + ">>")));
                 m_error = true;
             }
             else
             {
                 // 将需要解析单项的xxxFormatItem存入
                 m_items.push_back(it->second(std::get<1>(i)));
             }
         }

         // std::cout << "(" << std::get<0>(i) << ") - (" << std::get<1>(i) << ") - (" << std::get<2>(i) << ")" << std::endl;
     }
     // std::cout << m_items.size() << std::endl;
 }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值