【sylar服务器框架】日志模块初版工作流程

在这里插入图片描述

日志模块主要有三个类:
LogFormatter:根据给定的格式字符串形成一个个FormatItem,不同类别的FormatItem有其特定的格式化输出方式
LogAppender:输出目的地,内含一个LogFormatter,负责将格式化后的字符串输出到指定的目的地。目前该类有两个子类,分别将日志输出到控制台和文件中。
Logger:日志器。提供输出日志方法的上层类。含有多个输出地点LogAppender,以及一个用于赋值的LogFormatter。

一条指定等级的日志想要被输出,需要经过Logger和Appender两层筛选,这两个类中各含一个输出等级,只有当前日志等级>=这两个类中的等级,日志才会被输出到目的地。

logger中含有多个输出地 Appender。每个Appender 中包含了一个formatter。formatter的format()方法遍历每个formatItem项目,再调用formatItem的format() 返回每项的格式化字符串结果,然后根据所属的Appender目的地,输出到指定输出流中

/*日志对象*/
    class LogEvent {
    public:
        LogEvent(const char *fileName, int32_t linenum, uint32_t elapse, uint32_t
        threadID, uint32_t fiberID, uint64_t time);
        //定义指向LogEvent的智能指针 为类型 ptr
        typedef std::shared_ptr<LogEvent> ptr;

        const char* getFile(){ return m_file; }
        int32_t getLineNum(){ return m_line; }
        uint32_t  getElapse(){ return m_elapse; }
        uint32_t getThreadId(){ return m_threadId; }
        uint32_t getFiberId(){ return m_fiberId; }
        uint64_t getTime(){ return m_time; }
        const std::string getContent(){ return sstream.str(); }
        std::stringstream& getSS() { return sstream; }
    private:
        const char *m_file = nullptr; //当前文件路径
        int32_t m_line = 0; //当前行号
        uint32_t m_elapse = 0; // 程序从启动到现在的毫秒数
        uint32_t m_threadId = 0; //线程ID
        uint32_t m_fiberId = 0; //协程ID
        uint64_t m_time; //时间戳
        std::stringstream sstream;//内容

    };

LogEvent是日志本身。现有字段如上所示。
日志等级目前有5个。toString方法负责将等级对象转换成对应的字符串。

  class LogLevel {
    public:
        enum  Level{
            UNKNOW = 0,
            DEBUG = 1,
            INFO = 2,
            WARN = 3,
            ERROR = 4,
            FATAL = 5
        };
        static const char *toString(Level level);
    };

日志器是最终我们使用日志模块的入口。
它主要有这样几个字段:最低日志等级m_level,默认格式化器m_formatter,输出地集合m_appenders
主要方法是log(),负责将日志指定为某等级尝试输出到各个目的地。

 /*日志器 定义日志类别*/
    class Logger : public std::enable_shared_from_this<Logger> {
    public:
        typedef std::shared_ptr<Logger> ptr;
        Logger(const std::string &name = "root");
        
        void addAppender(LogAppender::ptr appender);
        void delAppender(LogAppender::ptr appender);
        LogLevel::Level  getLevel() const { return m_level; }
        void setLevel(LogLevel::Level level){m_level = level;}
        const std::string& getName(){ return m_name; }
        
        void log(LogLevel::Level level, const LogEvent::ptr event);
        void debug(LogEvent::ptr event);
        void info(LogEvent::ptr event);
        void warn(LogEvent::ptr event);
        void error(LogEvent::ptr event);
        void fatal(LogEvent::ptr event);
    private:
        std::string m_name;         
        LogLevel::Level m_level; //只有满足该日志级别的日志才会被它输出
        std::list<LogAppender::ptr> m_appenders;     //输出目的地的集合
        LogFormatter::ptr m_formatter; //默认的格式化器,用于赋值给 m_appenders 中的formatter
    };

LogAppender负责实际将日志输出到指定地点。
它的主要字段是:最低输出等级m_level 、格式化器m_formatter
其主要方法也是log(),

/*日志输出地*/
    class LogAppender {
    public:
        // 将来会有控制台和文件两种Appender ,析构的的方式不同
        virtual  ~LogAppender(){};
        typedef std::shared_ptr<LogAppender> ptr;
        //定义为纯虚函数,其子类必须实现该方法
        virtual void log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) =0;
        void  setFormatter(LogFormatter::ptr val){ m_formatter = val; }
        LogFormatter::ptr getFormatter() const  { return m_formatter; }
    protected: //此级别目的是为了使子类可见 外部不可见
        LogLevel::Level m_level = LogLevel::DEBUG;
        LogFormatter::ptr m_formatter;
    };

它有两个子类,分别输出到控制台和文件

//标准输出作为输出地点
    class StdoutLogAppender:public LogAppender {
    public:
        typedef std::shared_ptr<StdoutLogAppender> ptr;
        virtual void log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) override;
    private:
    };
    
//文件作为输出地点
    class FileLogAppender:public LogAppender {
    public:
        FileLogAppender(const std::string &filename);
        typedef std::shared_ptr<FileLogAppender> ptr;
        virtual void log(std::shared_ptr<Logger> logger ,LogLevel::Level level, LogEvent::ptr event) override;
        bool reopen(); // 如果已经打开了,就先关闭再打开
    private:
        std::string m_filename;
        std::ofstream m_filestream;
    };

LogFormatter为日志格式化器。
主要字段:格式化字符串 m_pattern ,日志每一项的格式化方式m_items
主要方法:init()解析指导日志格式的格式字符串。

//日志格式器
    class LogFormatter {
    public:
        typedef std::shared_ptr<LogFormatter> ptr;
        // virtual ~LogFormatter(){};
        LogFormatter(const std::string &pattern);
        std::string format(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event); //返回格式化后的字符串
        void init();//解析格式字符串,用来指导日志的输出方式
    public:
        class FormatItem {
        public:
            typedef std::shared_ptr<FormatItem> ptr;
            FormatItem(const std::string &fmt = "") {};
            virtual ~FormatItem() {};
            virtual void format(std::ostream &ostream,std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event)=0;
        };

    private:
        
        std::vector<FormatItem::ptr> m_items;//存放日志的各个部分
        std::string m_pattern; // 指明格式化方式的字符串
        int char_cat(char ch);
        
        enum status{
            ST_ERROR = -2,
            ST_BREAK =-1,
            ST_TYPE_STR=0,
            ST_FORMAT_STR=1,
            ST_END=2
        };
        //                  SP      {               }               ch
        int nfa[3][5]={{ST_BREAK,ST_FORMAT_STR,ST_ERROR,ST_BREAK,ST_TYPE_STR},
                       {ST_ERROR,ST_ERROR,ST_END,ST_FORMAT_STR,ST_FORMAT_STR},
                       {ST_ERROR,ST_END,ST_END,ST_END,ST_END}};
    };

日志模块中主要的类已经介绍完了。下面看一看将一条日志输出需要经过哪些流程:

Logger::log → \rightarrow 遍历所有输出地 LogAppender::log → \rightarrow 对每个输出目的地中的格式化器 LogFormatter::format → \rightarrow 对格式化器中的日志项 列表FormatItem::format

  void Logger::log(LogLevel::Level level, const LogEvent::ptr event) {
        if (level >= m_level) {
            auto self = shared_from_this();
            for (auto &it : m_appenders) {
                it->log(self,level, event);
            }
        }
    }
 void StdoutLogAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
        if (level >= m_level) {
            std::cout << m_formatter->format(logger, level, event);
        }
    }
std::string LogFormatter::format(std::shared_ptr<Logger> logger, LogLevel::Level level, sylar::LogEvent::ptr event) {
        std::stringstream ss;
        for (auto &item : m_items) {
            item->format(ss, logger, level, event);
        }
        return ss.str();
    }

这里只列出两种FormatItem

class DateTimeFormatItem : public LogFormatter::FormatItem {
    public:
        DateTimeFormatItem(const std::string &formatstr = "%Y-%m-%d %H:%M:%S") : m_format(formatstr) {
            if (m_format.empty()) {
                m_format = "%Y-%m-%d %H:%M:%S";
            }
        }
        void format(std::ostream &ostream, std::shared_ptr<Logger> logger, LogLevel::Level level,
                    LogEvent::ptr event) override {
            struct tm tm;
            time_t time = event->getTime();
          //  localtime_r(&time,&tm)  //linux
            localtime_s(&tm, &time); //windows
            char buf[64];
            strftime(buf, sizeof(buf), m_format.c_str(), &tm);
            ostream << buf;
        }
    private:
        std::string m_format;
    };
/*====================================================================================*/
    class StringFormatItem : public LogFormatter::FormatItem {
    public:
        StringFormatItem(const std::string& str):FormatItem(str),m_string(str) {}
        void format(std::ostream &ostream, std::shared_ptr<Logger> logger, LogLevel::Level level,
                    LogEvent::ptr event) override {
            ostream << m_string;
        }
    private:
        std::string m_string;
    };

现在的问题就是,LogFormatter::format中遍历的m_items 是怎么得到的?

这就要求助于 formatter 中的init()函数了,它根据格式字符串,先是为日志的每个部分生成了一个3元组,再将这些三元组按顺序填充了m_items

格式字符串 "%d{%Y-%m-%d}%T[%p]%T<%f : %l>%T%m%n"

各个部分的3元组:

(d) - (%Y-%m-%d) - (1)
(T) - () - (1)
([) - () - (0)
(p) - () - (1)
(]) - () - (0)
(T) - () - (1)
(<) - () - (0)
(f) - () - (1)
( : ) - () - (0)
(l) - () - (1)
(>) - () - (0)
(T) - () - (1)
(m) - () - (1)
(n) - () - (1)
2021-06-16      [DEBUG] <C:\Users\huyaorui\CLionProjects\SylarServer\sylar\testLog.cpp : 14>    hello sylar-log
2021-06-16      [ERROR] <C:\Users\huyaorui\CLionProjects\SylarServer\sylar\testLog.cpp : 18>    something wrong!!

init()方法负责解析格式字符串,将每个部分拆成一个个三元组,最后根据类型字符串来选择不同类型的FormatItem加入到有序列表m_items中。

首先我们来观察格式字符串中的一项 %d{%Y-%m-%d}
其形式为%X{...}
%后的字符表示类别%d表示这项是日期时间。不妨将其称作类型字符串
{ }中的内容是指导用何种样式来输出日期时间。不妨将其称为格式字符串

init解析出格式每项,将其放入到三元组( 类型字符串,格式字符串,是否按字面值输出 )有序列表中,而后初始化一个MAP,将其负责将三元组按类型字符映射为其对应类别的XxxFormatItem对象,并放入有序列表m_items

下表为解析日志格式化字符串的状态机:
在这里插入图片描述

 void LogFormatter::init() {
        std::vector<std::tuple<std::string, std::string, int>> vector;//三元组有序列表(类型字符串,格式字符串,是否按字面值输出)
        std::string nstr;//主要负责接受格式字符串中按字面值输出的普通字符串(就是除了%、{、} )
        int state ;//状态
        for (size_t i = 0; i < m_pattern.size(); ++i) {

            if (m_pattern[i] != '%') { //只要没遇见 % ,就将所读到的字符加到nstr
                nstr.append(1, m_pattern[i]);
                continue;
            }

            if (i + 1 < m_pattern.size() && m_pattern[i + 1] == '%') {//如果是出现了连续两个%% ,视作%字符本身,也加入nstr
                nstr.append(1, '%');
                continue;
            }
            /*
             * 只有遇到%后才能进入下面这部分!! 表示开始解析形如 %X{...} 的部分
            */
            state = ST_TYPE_STR; //初始状态 = 类型字符串 %XX
            size_t n = i + 1;//从%后的第一个字符开始
            std::string typeStr;
            std::string formatStr;
            int formatStart = -1; //如果有格式字符串,其开始的下标

            // 在字符串没读完的情况下,这三种状态都可能使循环结束
            while (state != ST_BREAK && state != ST_END && state != ST_ERROR && n < m_pattern.size()) {
                int cat = char_cat(m_pattern[n]);//判断当前读入字符的类别
                if (cat >= 0) {
                    state = nfa[state][cat];
                } else {//如果不属于空格、{、} 、字母、%、- 中的任何一个,则将状态置为 ST_BREAK;
//                    state = ST_ERROR;
//                    break;
                    state = ST_BREAK;
                }

                switch (state) {
                    case ST_FORMAT_STR:
                        if (formatStart == -1 ) {//当首次读到{ 从而状态跳变为ST_FORMAT_STR时执行:
                            typeStr = m_pattern.substr(i + 1, n - i -1);
                            formatStart = n; //字符 { 的位置
                        }
                        break;
                    case ST_END:
                        formatStr = m_pattern.substr(formatStart + 1, n - formatStart - 1);
                        break;
                }
                ++n;
            }

            if (n >= m_pattern.size()) { //若是因为读到字符串的末尾而跳出while循环,则有可能没能解析完最后的部分
                if (!nstr.empty()) {
                    vector.push_back(std::make_tuple(nstr, "", 0));
                    nstr.clear();
                }
                if (formatStart != -1) { n = formatStart; } //若这最后一部分含格式字符串{xxx},则应将n置为 { 的位置,这样才能保证下面能正确地将typeStr取出
                typeStr = m_pattern.substr(i + 1, n - i - 1);
                vector.push_back(std::make_tuple(typeStr, formatStr, 1));
                i = m_pattern.size();
                break;
            }



            if (state == ST_BREAK) {
                if (!nstr.empty()) {
                    vector.push_back(std::make_tuple(nstr, "", 0));
                    nstr.clear();
                }
                typeStr = m_pattern.substr(i + 1, n - i - 2);
                vector.push_back(std::make_tuple(typeStr, formatStr, 1));
                i = n-2;//为抵消while中的 ++n和外层for 中的++i
            } else if (state == ST_ERROR) {
                std::cout << "Format string error:" << m_pattern << " - " << m_pattern.substr(i) << std::endl;
                vector.push_back(std::make_tuple("<<Mode declaration error>>", formatStr, 0));
            } else if (state == ST_END) { //完整地解析了%X{xxx}这个部分
                if (!nstr.empty()) {
                    vector.push_back(std::make_tuple(nstr, "", 0));
                    nstr.clear();
                }
                vector.push_back(std::make_tuple(typeStr, formatStr, 1));
                i = n-1; //while中的 ++n 导致多读了一位
                //此时i所对应的字符是},因此不用再-1去抵消掉for 中的++i
            }
     }

        static std::map<std::string, std::function<FormatItem::ptr(const std::string &str)> > s_format_items = {
#define YY(str, CLASS)\
                    { #str, [](const std::string &fmt) {return FormatItem::ptr(new CLASS(fmt));}  }

                YY(m, MessageFormatItem),
                YY(p, LevelFormatItem),
                YY(r, ElapseFormatItem),
                YY(c, NameFormatItem),
                YY(t, ThreadFormatItem),
                YY(n, NewLineFormatItem),
                YY(d, DateTimeFormatItem),
                YY(f, FileFormatItem),
                YY(l, LineNumFormatItem),
                YY(T, TabFormatItem),
#undef YY
        };


        for (auto &i : vector) {
            if (std::get<2>(i) == 0) { // 三元组的最后一个元素==0 这表示它按照字面值输出即可,不需要格式化输出
                m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(i))));//直接把三元组中的第一位交给StringFormatItem 做初始化
            } else { //三元组中的第一位是类型字符,需要格式化后输出
                auto item = s_format_items.find(std::get<0>(i)); //在MAP中用格式字符串寻找对应的FormatItem对象
                
                if (item==s_format_items.end()) {//没有找到
                    m_items.push_back(FormatItem::ptr(new StringFormatItem("<<Unsupported format:  %"+std::get<0>(i)+">>")));
                } else { //找到了
                    m_items.push_back(item->second(std::get<1>(i))); //用三元组中的第2个元素:格式字符串{xxx} 来创建对应类别的FormatItem
                }
            }
		//测试:打印出三元组
       //  std::cout <<"("<< std::get<0>(i) << ") - (" << std::get<1>(i) << ") - (" << std::get<2>(i)<<")" << std::endl;

        }
    }
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值