• sylar高性能服务器-日志(P1-P6)代码解析+调试分析


    ​ 本篇文章主要针对一下sylar高性能服务器项目视频p1-p6的代码分析以及调试,大佬在讲课时基本上都是码代码,很少去详细的讲解代码的细节。像我这种没接触过服务器项目的小白看起来都很有压力,还别说能运行。下面的内容一到六是我自己对当前视频代码的一个简单分析,可能很多地方理解的不对,大家自己取舍。第七部分则是使用gdb进行调试,观察给出的示例是如何运行的,不然照着抄一遍基本看不懂写的什么,如果你写完代码甚至不知道每个模块具体做了什么,推荐可以参考我调试的步骤,没接触过gdb也无妨,只需要知道几个简单命令就行。
    ​ 最后我在附录中留了前面6节视频完整代码,在保证环境配置正确下可以完美进行调试。

    项目视频链接

    sylar

    一、整体结构

    日志模块类似于Log4j,目前的结构如下:

    |–LogEvent
    |–LogLevel
    |–LogFormatter
    |–LogAppender
    |–StdoutAppender
    |–FileLogAppender
    |–Logger

    • LogEvent:日志的详细描述,包括文件名、线程ID、程序启动时间等一系列的信息
    • LogLevel:定义日志的级别
    • LogFormatter:定义日志的格式化输出
    • LogAppender:控制日志的输出地
      1. StdoutAppender:将日志输出到控制台
      2. FileLogAppender:将日志输出保存到文件
    • Logger:日志器,可以添加多个LogAppender和LogFormatter对象将其输出到多个地方

    这里引用一下博主[令人头疼的编程]画的图,非常直观。

    在这里插入图片描述

    sylar在写代码时大量用到了智能指针,同样引用一下上面博主画的类之间的指针结构图,看代码时参照会轻松很多。

    img

    二、LogEvent

    下列是目前定义的日志事件所包含的一些内容,可以看到sylar在定义变量时非常规范,常量、变量的范围、有符号或无符号都分得很清楚。

    const char* m_file = nullptr;   // 文件名
    int32_t m_line = 0;             // 行号,引入头文件stdint.h
    uint32_t m_eplase = 0;          // 程序启动到现在的毫秒
    uint32_t m_threadId = 0;        // 线程ID
    uint32_t m_fiberId = 0;         // 协程ID
    uint64_t m_time;                // 时间戳
    std::stringstream m_ss;         // 内容
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    为了防止不同编译器中int所占的大小不同,在定义这些变量时基本使用重定义模式,而对于一些不应该为负数的变量,我们应该定义为无符号数,减少程序的漏洞。

    sylar在定义日志内容时使用了stringstream而不是string,这是因为对于日志内容的数据可能经常需要进行类型转换,使用stringstream会更加的方便和安全

    LogEvent的函数就比较简单,包括一个构造函数以及其它成员变量的取值函数。

    // log.h
    typedef std::shared_ptr<LogEvent> ptr; // [智能指针]
    LogEvent(const char* file, int32_t line, uint32_t eplase
    , uint32_t threadId, uint32_t fiberId, uint64_t time);
    
    const char* getFile() const { return m_file;}
    int32_t getLine() const { return m_line; }
    uint32_t getEplase() const { return m_eplase; }
    uint32_t getThreadId() const { return m_threadId; }
    uint32_t getFiberId() const { return m_threadId; }
    uint64_t getTime() const { return m_time; }
    const std::string getcContent() const { return m_ss.str(); }
    std::stringstream& getSS() { return m_ss; }
    
    // log.cc
    LogEvent::LogEvent(const char* file, int32_t line, uint32_t eplase
                , uint32_t threadId, uint32_t fiberId, uint64_t time) 
                :m_file(file),
                m_line(line),
                m_eplase(eplase),
                m_threadId(threadId),
                m_fiberId(fiberId),
                m_time(time) {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    三、LogLevel

    定义日志的级别,方便后期对不同的日志进行分类或者过滤接收某些日志。

    class LogLevel {
    public:
        enum Level{
            UNKNOW = 0,     //  未知 级别
            DEBUG = 1,      //  DEBUG 级别
            INFO = 2,       //  INFO 级别
            WARN = 3,       //  WARN 级别
            ERROR = 4,      //  ERROR 级别
            FATAL = 5       //  FATAL 级别
        };
    
        static const char* ToString(LogLevel::Level level);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ToString提供从日志级别到文本的转换,sylar使用了宏函数简化了繁琐的switch/case操作,以前看primer时也了解过宏函数的使用,不过属于看了就扔一边,平时写代码可以说基本没用过,第一次在真实项目中看到比较震撼,以后可以学着多用。后面代码中也有非常多的宏函数使用。

    const char* LogLevel::ToString(LogLevel::Level level) {
        switch(level) { // [宏函数的使用]
    #define XX(name) \
            case LogLevel::name: \
                return #name; \
                break;
        XX(DEBUG);
        XX(INFO);
        XX(WARN);
        XX(ERROR);
        XX(FATAL);
    #undef XX
        default:
            return "UNKONW";
        }
        return "UNKONW";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    四、LogFormatter

    FormatItem作为LogFormatter的公共内部类成员,将日志内容格式化。

    class LogFormatter {
    public:
        typedef std::shared_ptr<LogFormatter> ptr;
        LogFormatter(const std::string& pattern);
        std::string format(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event);
    public:
        class FormatItem {                          // [类成员]
        public:
            typedef std::shared_ptr<FormatItem> ptr;
            virtual ~FormatItem() {}
            virtual void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) = 0;
        };
    
        void init(); 
    private:
        std::string m_pattern;                      // 解析格式
        std::vector<FormatItem::ptr> m_items;       // 解析内容
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    格式化日志到流

    不同类别的format继承于FormatItem,并重写format函数。

    class MessageFormatItem : public LogFormatter::FormatItem { // 消息format
    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->getcContent();
        }
    };
    
    class LevelFormatItem : public LogFormatter::FormatItem { // 级别format
    public:
        LevelFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << LogLevel::ToString(level);
        }
    };
    
    class LineFormatItem : public LogFormatter::FormatItem { 
    public:
        LineFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << event->getLine();
        }
    };
    
    class StringFormatItem : public LogFormatter::FormatItem { 
    public:
        StringFormatItem(const std::string& str) 
            :m_string(str) {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << m_string;
        }
    private:
        std::string m_string;
    };
    
    class EplaseFormatItem : public LogFormatter::FormatItem { 
    public:
        EplaseFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << event->getEplase();
        }
    };
    
    class LoggerNameFormatItem : public LogFormatter::FormatItem { 
    public:
        LoggerNameFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << logger->getName();
        }
    };
    
    class ThreadIdFormatItem : public LogFormatter::FormatItem { 
    public:
        ThreadIdFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << event->getThreadId();
        }
    };
    
    class FiberIdFormatItem : public LogFormatter::FormatItem { 
    public:
        FiberIdFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << event->getFiberId();
        }
    };
    
    class DateTimeFormatItem : public LogFormatter::FormatItem { 
    public:
        DateTimeFormatItem(const std::string& format = "%Y:%m:%d %H:%M:%S") 
            : m_format(format) {
        }
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << event->getTime();
        }
    private:
        std::string m_format;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    init

    这个函数应该是前6节视频中最复杂的

    1. 首先创建一个存储三元组的容器vec,用于
    2. 第一个for循环
      • m_pattern是一个字符串,表示传入的格式模板,比如%Y:%m:%d %H:%M:%S,依次循环取m_pattern中的字符,分别判断普通字符、%、{}、空格等
      • (A) 代码对应有标记,给出了注释。
      • 进入到判断(B),说明当前字符是%,如果后面还有字符并且也是%,则不需要对当前的%作任何处理,进而去判断下一个%
      • 进入©,说明正好获取到了一个%,并且后面的字符不是%,那么就可以进行分析,这里定于了几个变量,直接看代码注释。
      • 进入(D)是一个while循环,从当前字符的下一个元素n开始,如果下标为n的字符不是字母,或者{},直接退出循环。如果fmt_status为0,表示还没有遇到过{,进入语句,如果当前字符为{,则截取一段字符串,范围为(i + 1, n - i - 1),i是进入(D)之前的左边界下标,n是在while循环里变化的右边界下标,每当遇到一个{,就会把它左边的字符全部放入内容字符串str中,然后把fmt_status赋值1,fmt_begin赋值为n,记录下一个开始的位置。
      • 如果fmt_status为1,并且遇到了},同样也是截取一段字符串,赋值给fmt,这时fmt就表示大括号里面的字符,最后把状态置为2,表示处理完了括号里面的数据。
      • 整个while循环的目的就是为了解析时间格式,我们传入的模板比如是{%d{%Y-%m-%d …}},经过这段代码,str = d,表示正在解析的是时间,fmt就等于解析后的时间格式%Y-%m-%d ...
      • 进入(E),退出了while循环,如果状态为0,表示要解析后面的字符,那么就要先把之前解析的内容存入vec容器中;如果状态为1,说明传入的格式模板不正确,输出错误提示,并往vec中写入一个错误信息;如果状态为2,这里感觉代码重复了,后面sylar应该会修改。
    3. 进入(F),m_pattern的格式模板已经全部解析完成,定义一个map,第一个参数为相应的日志格式,第二个参数为FormatItem智能指针。里面存放不同类型的FormatItem,比如MessageFormatItem、LevelFormatItem,前面都有相应的定义。然后变量vec,先判断三元组第二个参数是否等于0,如果是说明这一条元组是一个类型字符代号(%d就是时间),存入到m_items中,然后调用相应的format方法。如果不是0,表示相应字符代号后面的内容,则去创建的map里面遍历,找到了就push到m_items,否则push一条错误信息。
    void LogFormatter::init() {
        std::vector<std::tuple<std::string, std::string,int>> vec;  // [tuple]  str:内容,format:格式模板, type:0表示格式模板中[]里面的字符代号,比如时间d,消息m,1表示代号跟着的内容
        std::string nstr; // 结果字符串
        for(size_t i = 0; i < m_pattern.size(); ++ i) {
            if(m_pattern[i] != '%') {		// (A)如果当前字符不是%,直接加入结果字符串nstr
                nstr.append(1,m_pattern[i]); // [append]
                continue;
            }
    
            if((i + 1) < m_pattern.size()) { // (B)
                if(m_pattern[i + 1] == '%') {
                    nstr.append(1, '%');
                    continue;
                }
            }
    
            size_t n = i + 1; 	    // (C) n表示后一个字符的位置
            int fmt_status = 0;     // 定义个状态初始为0 
            std::string str;        // 内容字符串,也就是传入格式模板的%m
            std::string fmt;        // 用于寻找括号
            size_t fmt_begin = 0;   //  记录当前下标的位置
    
            while(n < m_pattern.size()) { // (D)
                if(!isalpha(m_pattern[n]) && m_pattern[n] != '{' && m_pattern[n] != '}') { // 判断当前字符是否维空格
                    break;
                }
                if(fmt_status == 0) {	
                    if(m_pattern[n] == '{') { // 解析时间格式
                        str = m_pattern.substr(i + 1, n - i - 1); // str = d
                        fmt_status = 1; // 解析格式
                        fmt_begin = n;
                        ++ n;
                        continue;
                    }
                }
                if(fmt_status == 1) {
                    if(m_pattern[i] == '}') { // 结束解析时间格式
                        fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1); // fmt是时间格式,比如%Y-%m-%d之类
                        fmt_status = 2;
                        break;
                    }
                }
    
                ++ n;
    
            }
            if(fmt_status == 0) {  // (E)
                if(!nstr.empty()) {
                    vec.push_back(std::make_tuple(nstr, std::string(), 0));
                    nstr.clear();
                }
                str = m_pattern.substr(i + 1, n - i - 1);
                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("", fmt, 0));
            } else if(fmt_status == 2) {
                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;
            }
                
        }
        if(!nstr.empty()) {
            vec.push_back(std::make_tuple(nstr, std::string(), 0));
    
        }
        // [function] 引入function (F)
        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
    #undef XX
        };
        for(auto& i : vec) {
            if(std::get<2>(i) == 0) { // [?猜测是三元组下标取值]
                m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(i))));
            } else {
                auto it = s_format_items.find(std::get<0>(i));
                if(it == s_format_items.end()) {
                    m_items.push_back(FormatItem::ptr(new StringFormatItem("< + std::get<0>(i) + ">>")));
                } else {
                    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    五、LogAppender

    LogAppender定义将日志输出的目的地,包含两个子类StdoutAppender、FileLogAppender,分别将日志输出到控制台和文件,后期也可以增加其它的一些输出地。

    成员函数包括一个日志级别m_level和输出的格式器m_formatter。

    setFormatter用于定义日志输出使用的格式化方法。getFormatter获得格式化方法。

    class LogAppender {
    public:
        typedef std::shared_ptr<LogAppender> ptr;
        virtual ~LogAppender() {}                                           // 为了便于该类的派生类调用,定义为[虚类],
    
        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;                                      // 定义输出格式
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    输出到控制台

    m_formatter时一个vector,里面包含多个不同类型格式化,调用format函数时会依次遍历输出到流。

    这里的条件语句应该是根据日志级别进行一个简单的过滤

    // log.h
    class StdoutAppender : public LogAppender {
    public:
        typedef std::shared_ptr<StdoutAppender> ptr; 
        void log(Logger::ptr logger,LogLevel::Level level, LogEvent::ptr event) override;
    private:
    };
    
    // log.cc
    void StdoutAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
        if(level >= m_level) {
            std::cout << m_formatter->format(logger,level,event); // namespace "std" has no member "cout" 加入iostream
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    输出到文件

    成员包括文件名m_filename和文件输出流m_filestream。

    reopen函数用于打开一个文件,最后返回应该是判断文件打开是否成功,不知道这里用两个!是什么意思

    log函数用于输出到文件

    // log.h
    class FileLogAppender : public LogAppender {
    public:
        typedef std::shared_ptr<FileLogAppender> ptr;
        FileLogAppender(const std::string& filename);                   // 输出的文件名
        void log(Logger::ptr logger,LogLevel::Level level, LogEvent::ptr event) override;  // [override]
    
        bool reopen();                                                  // 重新打开文件,成功返回true
    private:
        std::string m_filename;
        std::ofstream m_filestream;                                     // 引入sstream
    
    };
    // log.cc
    bool FileLogAppender::reopen() {
        if(m_filestream) {
            m_filestream.close();
        }
    
        m_filestream.open(m_filename);
        return !!m_filestream; // [?]
    }
    void FileLogAppender::log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) {
        if(level >= m_level) {
            m_filestream << m_formatter->format(logger,level,event);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    六、Logger

    成员函数包含日志名称m_name、日志级别m_level、日志输出地集合m_appender、格式器m_formatter。

    构造函数进行一个简单初始化日志名、日志级别以及默认日志格式

    log函数会遍历m_appender里面的日志输出地,调用对应的Appender的log函数进行输出。

    addAppender函数会在当前日志item没有格式器时赋予一个默认值,否则直接加入m_appender集合

    delAppender函数删除一个指定的日志输出地

    // log.h
    class Logger : public std::enable_shared_from_this<Logger>{ // 为了传递智能指针参数
    public:
        typedef std::shared_ptr<Logger> ptr;
        
        Logger(const std::string& name = "root");
    
        void log(LogLevel::Level level, LogEvent::ptr event);
    
        // 不同级别的日志输出函数
        void debug(LogEvent::ptr event);
        void info(LogEvent::ptr event);
        void warn(LogEvent::ptr event);
        void fatal(LogEvent::ptr event);
        void error(LogEvent::ptr event);
    
        void addAppender(LogAppender::ptr appender);            // 添加一个appender
        void delAppender(LogAppender::ptr appender);            // 删除一个appender
        LogLevel::Level getLevel() const { return m_level; }    // [const放在函数后]
        void setLevel(LogLevel::Level val) { m_level = val; }   // 设置级别
    
        const std::string& getName() const { return m_name; }
    private:
        std::string m_name;                                     // 日志名称
        LogLevel::Level m_level;                                // 级别
        std::list<LogAppender::ptr> m_appender;                 // Appender集合,引入list
        LogFormatter::ptr m_formatter;						  // 格式器
    };
    // log.cc
    Logger::Logger(const std::string& name) 
            :m_name(name),
            m_level(LogLevel::DEBUG) {
        m_formatter.reset(new LogFormatter("%d [%p] <%f:%l> %m %n")); // 定义一个默认的日志格式
    }
    void Logger::log(LogLevel::Level level, LogEvent::ptr event)  {
        if(level >= m_level) {
            auto self = shared_from_this(); //  获得logger的智能指针 
            for(auto &i : m_appender) {
                i->log(self,level, event);
            } 
        }
    }
    void Logger::addAppender(LogAppender::ptr appender) {
        if(!appender->getFormatter()) {
            appender->setFormatter(m_formatter); // 保证每一个日志都有默认格式
        }
        m_appender.push_back(appender);
    }
    void Logger::delAppender(LogAppender::ptr appender) {
        for(auto it = m_appender.begin(); it != m_appender.end(); ++ it) {
            if(*it == appender) {
                m_appender.erase(it);
                break;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    七、调试

    调试需要用到cmake一系列的工具,我是用的阿里云服务器,很多库都有了,如果没有可以参考下列网站去安装。

    项目环境配置

    我会把到P6为止的代码贴在文章最后,如果环境没问题是可以编译运行的。下面我也简单写一下自己调试的步骤(已安装必须环境)。

    7.1调试步骤

    1. 在最外层sylar文件夹创建CMakeLists.txt,我的项目结构如下,红线划掉的是后面使用cmake命令生成的

      image-20231007150731552

      然后往CMakeLists.txt写入下列代码,不懂cmake的可以看看这篇文章:https://blog.csdn.net/weixin_43717839/article/details/128032486。

      cmake_minimum_required(VERSION 2.8)
      project(sylar)
      
      set(CMAKE_VERBOSE_MAKEFILE ON) 
      set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O0 -g -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function")
      
      set(LIB_SRC
          sylar/log.cc
          # sylar/util.cc
          )
      
      add_library(sylar SHARED ${LIB_SRC})
      #add_library(sylar_static STATIC ${LIB_SRC})
      #SET_TARGET_PROPERTIES(sylar_static PROPERTIES OUTPUT_NAME "sylar")
      
      add_executable(test tests/test.cc)
      add_dependencies(test sylar)
      target_link_libraries(test sylar)
      
      SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
      SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    2. 在tests文件夹创建一个test.cc文件,写入下列代码

      #include 
      #include "../sylar/log.h"
      
      int main(int argc, char** argv) {
          sylar::Logger::ptr logger(new sylar::Logger);
          logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender));
      
      
          sylar::LogEvent::ptr event(new sylar::LogEvent(__FILE__, __LINE__, 0, 1, 2, time(0)));
          // event->getSS() << "hello sylar log";
          logger->log(sylar::LogLevel::DEBUG, event);
          std::cout << "hello sylar log" << std::endl;
      
          return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    3. 然后在终端输入命令

      cmake .
      make
      
      • 1
      • 2
    4. 如果代码有问题,输入make后会报错,根据提示改就行。在能成功编译的情况下,会在bin文件夹生成一个可执行文件test

      image-20231007151246942

    5. 输入bin/test就可以运行,结果如下

      image-20231007151321030

    7.2尝试使用gdb调试

    参考视频以及别人的文章,一行一行把代码看了一遍,不过还是很蒙,所以打算调试一下看测试例子是如何一步一步解析的,正好学习一下gdb调试工具。

    没有用过gdb调试可以看看这篇文章https://blog.csdn.net/chen1415886044/article/details/105094688?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169666829816800182168185%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=169666829816800182168185&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-105094688-null-null.142v95chatgptT3_1&utm_term=gdb%E8%B0%83%E8%AF%95&spm=1018.2226.3001.4187

    image-20231007190539718

    测试gdb是否能执行

    1. 进入文件夹bin,输入下列命令,出现Reading sy…就说明已经加载成功

      image-20231007190459565

    2. 输入命令run(简写r),因为程序没有断点,直接输出结果

      image-20231007191242125

    开始调试

    test.cc文件代码如下,

    #include 
    #include "../sylar/log.h"
    
    int main(int argc, char** argv) {
        sylar::Logger::ptr logger(new sylar::Logger);
        logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender));
        sylar::LogEvent::ptr event(new sylar::LogEvent(__FILE__, __LINE__, 0, 1, 2, time(0)));
        // event->getSS() << "hello sylar log";
        logger->log(sylar::LogLevel::DEBUG, event);
        std::cout << "hello sylar log" << std::endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    调试一

    首先对sylar::Logger::ptr logger(new sylar::Logger);使用gdb进行调试

    1. 进入gdb模式,并在第5行打上一个断点,然后启动
      image-20231007194913157

    2. 输入s进入函数内部,输入l查看当前代码,并在132行继续添加一个断点

      image-20231007195117777

    3. 这里是一个日志器logger的构造函数,输入n进行逐语句执行(当前代码已经执行到了129行),直到遇到刚刚在132行设置的断点,并查看logger4个成员变量的值。可以看到,日志名称和级别都是使用的默认值,而日志输入地appender和格式器formatter目前都还没有初始化

      image-20231007195550628

    4. 目前已经到了第二个断点,这句代码是初始化logger的格式器formatter,括号里新建了一个LogFormatter对象,输入s进入LogFormatter的构造函数。m_pattern = %d [%p] <%f:%l> %m %n

      image-20231007200550479

    5. 语句现在执行到了203行,init函数会将第二个断点处(132行)传入格式模板进行解析,所以在这里204行设置一个断点,输入s进入函数内部

      image-20231007201004757

    6. 在init函数304行会对三元组容器vec(解析模板后的内容)进行遍历打印,为了方便我们在这里也加入一个断点

      image-20231007201412398

    7. [%](现在每一个循环遍历的元素我都写在序号后)下面进入init函数的第一个for循环,查看每一次循环后三元组vec、字符标记(例如[d],[m]之类的,)str,字符标记后面的内容fmt、以及nstr。一直输入n直到一次循环结束。m_pattern = %d [%p] <%f:%l> %m %n,第一次循环解析的值为’%‘,那么跳过第一个if语句,进入第二个if语句,但是由于下一个字符不是’%‘,继续执行解析当前字符’%’

      image-20231007202333737

      [d]进入while循环,不满足里面的条件判断,n++

      [ ]遇到空格,触发第一个条件判断,跳出while循环,执行263行代码if(!nstr.empty())

      image-20231007204211547

      fmt_status = 0,表示没有遇到{}里面的内容,当前str = ’d’,nstr为空

      image-20231007204605833

      到这里第一次循环结束,vec里面有一个元组,解析到了格式模板里面的时间字符d

      image-20231007204920892

    8. [ ]上一个循环结尾i = n - 1 = 1,经历一个i++,i = 2所以当前元素是空格,进入第一个条件判断,把空格字符加入nstr,结束当前for循环

      image-20231007205328552

    9. [ [ ] i = 3遇到括号[, 继续加入nstr,然后结束当前循环,目前nstr = " ["

    10. [ % ] 遇到%,并且下一个不是%,说明下一个是一个字符标记(p,日志级别),进入while循环,和上面第一次for循环类似,就不详细说明,依次遍历p、]然后遇到一个空格跳出while循环。进入263行代码,目前nstr = " [",所以需要把nstr加入到vec中

      image-20231007210208506

      现在vec就有两条记录,刚加入的nstr第三个参数为0,可以理解为非字符标记,也就是实际内容。

      image-20231007210300121

      然后继续截取str = p,这是一个字符标记,同样也写入vec中

      image-20231007210420896

    11. ]继续循环,当前字符为],后面的循环依次把"] <"三个字符加入nstr中

      image-20231008083801978

    12. [ % ]遇到%,下一个字符为f,则说明遇到字符标记,把之前存入nstr的"] <"存入vec中

      image-20231008084422018

      然后从while出来后(遇到了空格跳出),str = f,将其存入vec

      image-20231008085248023

    13. 后面的for循环解析格式模板我就跳过了,大致就是遇到%就判断是否是字符标记,遇到空格就把之间 的nstr存入vec,然后再提取字符标记str,再存入vec;如果没有遇到%,就直接把当前元素存入nstr,直到下一次遇到%再做处理。结束for循环后,vec容器存储如下

      image-20231008085704942

    14. [“d 1”]现在进入断点304,也就是根据我们自定义的map,把vec容器的元素解析为不同的类型formatter(例如消息、时间、level等)。对于vec中的第一个元素“d 1",进入条件判断中的else部分

      image-20231008090208632

      在map中查找是否存储key为”d“的键值对,找到后实例化对应的DateTimeFormatItem,然后把指针存入到m_items中。遍历完vec容器,最后就得到了一个解析完格式模板后的一个指针容器m_items。

    调试2

    对语句logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender));进行调试,回顾一下logger的初始化,我们主要是解析传入的默认格式模板,然后把结果存入指针容器m_items。下图就是vec容器的输出,那么m_items里就存放在例如MessageFormatItem等FormatItem

    image-20231008092205735

    logger的四个参数除了m_appender以外都进行了初始化,下面就是给logger添加一个日志输出地appender。

    1. 还是在添加appender的语句打一个断点。然后输入s进入函数内部,

      image-20231008092747950

      传入的参数是一个输出到控制台的appender,因此进入StdoutAppender类,进行初始化(使用默认构造函数)

      image-20231008092943563

      appender初始化完成后,调用logger的方法添加,并把之前解析出来的m_formatter存入到该appender中

      image-20231008094607241

    调试3

    针对语句sylar::LogEvent::ptr event(new sylar::LogEvent(__FILE__, __LINE__, 0, 1, 2, time(0)));

    到此一个logger已经完完全全构建好了

    1. 在改语句打断点,进入到event的构造函数,传入的参数前两个因为目前还没有,传入占位符,后3个分别是程序启动的毫秒:0、线程ID:1、携程ID:2、内容默认为空。进入构造函数内部

      image-20231008095743832

    调试4

    针对语句logger->log(sylar::LogLevel::DEBUG, event);

    1. 在目标语句打上断点,进入logger的log函数

      image-20231008100128113

      image-20231008100135140

    2. 遍历m_appender容器,我们只有一个,所以进入相应appender的log函数

      image-20231008100252719

      在log函数里面调用m_formatter的format方法

      image-20231008100546737

      来到下面函数,这里代码是错误的,需要把return ss.str()放在for循环后,否则只能打印一个时间

      image-20231008100742808

    3. m_items存放的是之前解析的指向格式模板的指针

      image-20231008100851171

      第一个解析的是日期d,所以进入DateTimeFormatItem,调用里面的format进行输出,在时间的format方法里面,会获取当前时间戳,然后转换为本地时间,存入到流中

      image-20231008101432173

    4. 其它的也是相应的formatter进行打印,遍历完appender后,往控制台打印了解析的结果如下

      image-20231008103742014

    八、附录

    8.1log.h

    #ifndef __SYLAR_LOG_H__
    #define __SYLAR_LOG_H__
    
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    
    namespace sylar{ // 防止和别人的代码冲突
    
    class Logger;  // <把Logger放到这里的目的?> 前面的一些类会用到Logger,不加会报未定义错误
    // 日志的一些配置
    class LogEvent {
    public:
        typedef std::shared_ptr<LogEvent> ptr; // [智能指针]
        LogEvent(const char* file, int32_t line, uint32_t eplase
                , uint32_t threadId, uint32_t fiberId, uint64_t time);
    
        const char* getFile() const { return m_file;}
        int32_t getLine() const { return m_line; }
        uint32_t getEplase() const { return m_eplase; }
        uint32_t getThreadId() const { return m_threadId; }
        uint32_t getFiberId() const { return m_threadId; }
        uint64_t getTime() const { return m_time; }
        const std::string getcContent() const { return m_ss.str(); }
        std::stringstream& getSS() { return m_ss; }
    private:
        const char* m_file = nullptr;   // 文件名
        int32_t m_line = 0;             // 行号,引入头文件stdint.h
        uint32_t m_eplase = 0;          // 程序启动到现在的毫秒
        uint32_t m_threadId = 0;         // 线程ID
        uint32_t m_fiberId = 0;          // 协程ID
        uint64_t m_time;                // 时间戳
        std::stringstream m_ss;          // 内容
    };
    
    // 自定义日志级别
    class LogLevel {
    public:
        enum Level{
            UNKNOW = 0,     //  未知 级别
            DEBUG = 1,      //  DEBUG 级别
            INFO = 2,       //  INFO 级别
            WARN = 3,       //  WARN 级别
            ERROR = 4,      //  ERROR 级别
            FATAL = 5       //  FATAL 级别
        };
    
        static const char* ToString(LogLevel::Level level);
    };
    
    // 日志格式
    class LogFormatter {
    public:
        typedef std::shared_ptr<LogFormatter> ptr;
        LogFormatter(const std::string& pattern);
        std::string format(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event);
    public:
        class FormatItem {                          // [类中类]
        public:
            typedef std::shared_ptr<FormatItem> ptr;
            virtual ~FormatItem() {}
            virtual void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) = 0;
        };
    
        void init(); 
    private:
        std::string m_pattern;                      // 解析格式
        std::vector<FormatItem::ptr> m_items;       // 解析内容
    };
    
    // 日志输出地
    class LogAppender {
    public:
        typedef std::shared_ptr<LogAppender> ptr;
        virtual ~LogAppender() {}                                           // 为了便于该类的派生类调用,定义为[虚类],
    
        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 Logger : public std::enable_shared_from_this<Logger>{ // [?]
    public:
        typedef std::shared_ptr<Logger> ptr;
        
        Logger(const std::string& name = "root");
    
        void log(LogLevel::Level level, LogEvent::ptr event);
    
        // 不同级别的日志输出函数
        void debug(LogEvent::ptr event);
        void info(LogEvent::ptr event);
        void warn(LogEvent::ptr event);
        void fatal(LogEvent::ptr event);
        void error(LogEvent::ptr event);
    
        void addAppender(LogAppender::ptr appender);            // 添加一个appender
        void delAppender(LogAppender::ptr appender);            // 删除一个appender
        LogLevel::Level getLevel() const { return m_level; }    // [const放在函数后]
        void setLevel(LogLevel::Level val) { m_level = val; }   // 设置级别
    
        const std::string& getName() const { return m_name; }
    private:
        std::string m_name;                                     // 日志名称
        LogLevel::Level m_level;                                // 级别
        std::list<LogAppender::ptr> m_appender;                 // Appender集合,引入list
        LogFormatter::ptr m_formatter;
    };
    
    // 输出方法分类
    
    // 输出到控制台
    class StdoutAppender : public LogAppender {
    public:
        typedef std::shared_ptr<StdoutAppender> ptr; 
        void log(Logger::ptr logger,LogLevel::Level level, LogEvent::ptr event) override;
    private:
    };
    
    // 输出到文件
    class FileLogAppender : public LogAppender {
    public:
        typedef std::shared_ptr<FileLogAppender> ptr;
        FileLogAppender(const std::string& filename);                   // 输出的文件名
        void log(Logger::ptr logger,LogLevel::Level level, LogEvent::ptr event) override;  // [override]
    
        bool reopen();                                                  // 重新打开文件,成功返回true
    private:
        std::string m_filename;
        std::ofstream m_filestream;                                     // stringstream要报错,引入sstream
    
    };
    }
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144

    8.2log.cc

    #include"log.h"
    #include
    #include
    #include
    #include
    #include
    
    namespace sylar {
    
    const char* LogLevel::ToString(LogLevel::Level level) {
        switch(level) { // [宏函数的使用]
    #define XX(name) \
            case LogLevel::name: \
                return #name; \
                break;
        XX(DEBUG);
        XX(INFO);
        XX(WARN);
        XX(ERROR);
        XX(FATAL);
    #undef XX
        default:
            return "UNKONW";
        }
        return "UNKONW";
    }
    
    
    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->getcContent();
        }
    };
    
    class LevelFormatItem : public LogFormatter::FormatItem { 
    public:
        LevelFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << LogLevel::ToString(level);
        }
    };
    
    class LineFormatItem : public LogFormatter::FormatItem { 
    public:
        LineFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << event->getLine();
        }
    };
    
    class StringFormatItem : public LogFormatter::FormatItem { 
    public:
        StringFormatItem(const std::string& str) 
            :m_string(str) {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << m_string;
        }
    private:
        std::string m_string;
    };
    
    class EplaseFormatItem : public LogFormatter::FormatItem { 
    public:
        EplaseFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << event->getEplase();
        }
    };
    
    class LoggerNameFormatItem : public LogFormatter::FormatItem { 
    public:
        LoggerNameFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << logger->getName();
        }
    };
    
    class ThreadIdFormatItem : public LogFormatter::FormatItem { 
    public:
        ThreadIdFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << event->getThreadId();
        }
    };
    
    class FiberIdFormatItem : public LogFormatter::FormatItem { 
    public:
        FiberIdFormatItem(const std::string& fmt = "") {}
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            os << event->getFiberId();
        }
    };
    
    class DateTimeFormatItem : public LogFormatter::FormatItem { 
    public:
        DateTimeFormatItem(const std::string& format = "%Y:%m:%d %H:%M:%S") 
            : m_format(format) {
                if(m_format.empty()) {
                    m_format = "%Y:%m:%d %H:%M:%S";
                }
        }
        void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
            // os << event->getTime();
            struct tm tm;
            time_t time = event->getTime(); // 当前时间戳 引入time.h
            localtime_r(&time, &tm); // 将时间戳转换为本地时间,并将结果存放在tm中
            char buf[64];
            strftime(buf, sizeof(buf), m_format.c_str(), &tm);
            os << buf;
        }
    private:
        std::string m_format;
    };
    
    LogEvent::LogEvent(const char* file, int32_t line, uint32_t eplase
                , uint32_t threadId, uint32_t fiberId, uint64_t time) 
                :m_file(file),
                m_line(line),
                m_eplase(eplase),
                m_threadId(threadId),
                m_fiberId(fiberId),
                m_time(time) {
    
    }
    
    
    Logger::Logger(const std::string& name) 
            :m_name(name),
            m_level(LogLevel::DEBUG) {
        m_formatter.reset(new LogFormatter("%d [%p] <%f:%l> %m %n"));
    }
    
    void Logger::addAppender(LogAppender::ptr appender) {
        if(!appender->getFormatter()) {
            appender->setFormatter(m_formatter); // 保证每一个日志都有默认格式
        }
        m_appender.push_back(appender);
    }           
    void Logger::delAppender(LogAppender::ptr appender) {
        for(auto it = m_appender.begin(); it != m_appender.end(); ++ it) {
            if(*it == appender) {
                m_appender.erase(it);
                break;
            }
        }
    }
    
    void Logger::log(LogLevel::Level level, LogEvent::ptr event)  {
        if(level >= m_level) {
            auto self = shared_from_this(); // [?] 
            for(auto &i : m_appender) {
                i->log(self,level, event);
            } 
        }
    }
    
    void Logger::debug(LogEvent::ptr event) {
        log(LogLevel::DEBUG, event); 
    }
    void Logger::info(LogEvent::ptr event) {
        log(LogLevel::INFO, event); 
    }
    void Logger::warn(LogEvent::ptr event) {
        log(LogLevel::WARN, event); 
    }
    void Logger::fatal(LogEvent::ptr event) {
        log(LogLevel::ERROR, event); 
    }
    void Logger::error(LogEvent::ptr event) {
        log(LogLevel::FATAL, event); 
    }
    
    
    FileLogAppender::FileLogAppender(const std::string& filename) 
        : m_filename(filename) {
    
    }
    
    void FileLogAppender::log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) {
        if(level >= m_level) {
            m_filestream << m_formatter->format(logger,level,event);
        }
    }
    
    bool FileLogAppender::reopen() {
        if(m_filestream) {
            m_filestream.close();
        }
    
        m_filestream.open(m_filename);
        return !!m_filestream; // [?]
    }
    
    void StdoutAppender::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::endl; // namespace "std" has no member "cout" 加入iostream
        }
    }
    
    LogFormatter::LogFormatter(const std::string& pattern) 
        : m_pattern(pattern) {
            init();
    }
    
    std::string LogFormatter::format(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) {
        std::stringstream ss;
        for(auto &i : m_items) {
            i->format(ss,logger,level,event);
          
        }
        return ss.str();
    }
    
    // 日志格式定义
    void LogFormatter::init() {
        std::vector<std::tuple<std::string, std::string,int>> vec;  // [tuple]  str,format, type
        std::string nstr; //当前str
        for(size_t i = 0; i < m_pattern.size(); ++ i) {
            if(m_pattern[i] != '%') {
                nstr.append(1,m_pattern[i]); // [append]
                continue;
            }
    
            if((i + 1) < m_pattern.size()) {
                if(m_pattern[i + 1] == '%') {
                    nstr.append(1, '%');
                    continue;
                }
            }
    
            size_t n = i + 1;
            int fmt_status = 0;
            std::string str;
            std::string fmt;
            size_t fmt_begin = 0; // 
    
            while(n < m_pattern.size()) {
                if(!isalpha(m_pattern[n]) && m_pattern[n] != '{' && m_pattern[n] != '}') { // 判断当前字符是否维空格
                    break;
                }
                if(fmt_status == 0) {
                    if(m_pattern[n] == '{') {
                        str = m_pattern.substr(i + 1, n - i - 1);
                        fmt_status = 1; // 解析格式
                        fmt_begin = n;
                        ++ n;
                        continue;
                    }
                }
                if(fmt_status == 1) {
                    if(m_pattern[i] == '}') {
                        fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1);
                        fmt_status = 2;
                        break;
                    }
                }
    
                ++ n;
    
            }
            if(fmt_status == 0) {
                if(!nstr.empty()) {
                    vec.push_back(std::make_tuple(nstr, std::string(), 0));
                    nstr.clear();
                }
                str = m_pattern.substr(i + 1, n - i - 1);
                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("", fmt, 0));
            } else if(fmt_status == 2) {
                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;
            }
                
        }
        if(!nstr.empty()) {
            vec.push_back(std::make_tuple(nstr, std::string(), 0));
        }
        // [function] 引入function
        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
    #undef XX
        };
        for(auto& i : vec) {
            if(std::get<2>(i) == 0) {
                m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(i))));
            } else {
                auto it = s_format_items.find(std::get<0>(i));
                if(it == s_format_items.end()) {
                    m_items.push_back(FormatItem::ptr(new StringFormatItem("< + std::get<0>(i) + ">>")));
                } else {
                    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;
        }
    }
    }
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321

    8.3test.cc

    #include 
    #include "../sylar/log.h"
    
    int main(int argc, char** argv) {
        sylar::Logger::ptr logger(new sylar::Logger);
        logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender)); 
    
        sylar::LogEvent::ptr event(new sylar::LogEvent(__FILE__, __LINE__, 0, 1, 2, time(0)));
        // event->getSS() << "hello sylar log";
        logger->log(sylar::LogLevel::DEBUG, event);
        // std::cout << "hello sylar log" << std::endl;
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    8.4Cmakelists.txt

    cmake_minimum_required(VERSION 2.8)
    project(sylar)
    
    set(CMAKE_VERBOSE_MAKEFILE ON) 
    set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O0 -g -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function")
    
    set(LIB_SRC
        sylar/log.cc
        # sylar/util.cc
        )
    
    add_library(sylar SHARED ${LIB_SRC})
    #add_library(sylar_static STATIC ${LIB_SRC})
    #SET_TARGET_PROPERTIES(sylar_static PROPERTIES OUTPUT_NAME "sylar")
    
    add_executable(test tests/test.cc)
    add_dependencies(test sylar)
    target_link_libraries(test sylar)
    
    SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
    SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    d::get<0>(i) << “) - (” << std::get<1>(i) << “) - (” << std::get<2>(i) << ‘)’ << std::endl;
    }
    }
    }

    
    ### 8.3test.cc
    
    ```c
    #include 
    #include "../sylar/log.h"
    
    int main(int argc, char** argv) {
        sylar::Logger::ptr logger(new sylar::Logger);
        logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender)); 
    
        sylar::LogEvent::ptr event(new sylar::LogEvent(__FILE__, __LINE__, 0, 1, 2, time(0)));
        // event->getSS() << "hello sylar log";
        logger->log(sylar::LogLevel::DEBUG, event);
        // std::cout << "hello sylar log" << std::endl;
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    8.4Cmakelists.txt

    cmake_minimum_required(VERSION 2.8)
    project(sylar)
    
    set(CMAKE_VERBOSE_MAKEFILE ON) 
    set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O0 -g -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function")
    
    set(LIB_SRC
        sylar/log.cc
        # sylar/util.cc
        )
    
    add_library(sylar SHARED ${LIB_SRC})
    #add_library(sylar_static STATIC ${LIB_SRC})
    #SET_TARGET_PROPERTIES(sylar_static PROPERTIES OUTPUT_NAME "sylar")
    
    add_executable(test tests/test.cc)
    add_dependencies(test sylar)
    target_link_libraries(test sylar)
    
    SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
    SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    web网页设计期末课程大作业 我的美丽家乡盐城 HTML+CSS+JavaScript
    微信小程序开发:异步处理接入的生成式图像卡通化
    QT+OSG/osgEarth编译之三十三:HDF5+Qt编译(一套代码、一套框架,跨平台编译,版本:HDF5-1.12.2)
    说企业自研应用是误区的,非蠢即坏
    多线程的三种创建方式&守护线程
    ES13的11个超赞的新属性
    Cloud
    [源码解析] TensorFlow 分布式之 MirroredStrategy 分发计算
    un9.2:JavaScript基础用法。
    【设计模式】设计模式
  • 原文地址:https://blog.csdn.net/qq_18824403/article/details/133681650