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

news2024/11/24 10:58:15

文章目录

    • 一、整体结构
    • 二、LogEvent
    • 三、LogLevel
    • 四、LogFormatter
    • 五、LogAppender
    • 六、Logger
    • 七、调试
      • 7.1调试步骤
      • 7.2尝试使用gdb调试
    • 八、附录
      • 8.1log.h
      • 8.2log.cc
      • 8.3test.cc
      • 8.4Cmakelists.txt
      • 8.4Cmakelists.txt

​ 本篇文章主要针对一下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;         // 内容

为了防止不同编译器中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) {

}

三、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);
};

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";
}

四、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;       // 解析内容
};

格式化日志到流

不同类别的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;
};

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("<parse_error>", 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("<<error_format %" + 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;
    }
}

五、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;                                      // 定义输出格式
};

输出到控制台

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
    }
}

输出到文件

成员包括文件名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);
    }
}

六、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;
        }
    }
}

七、调试

调试需要用到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)
    
    
  2. 在tests文件夹创建一个test.cc文件,写入下列代码

    #include <iostream>
    #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;
    }
    
  3. 然后在终端输入命令

    cmake .
    make
    
  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 <iostream>
#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;
}

调试一

首先对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<string>
#include<stdint.h>
#include<memory>
#include<list>
#include<vector>
#include<sstream>
#include<fstream>

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

8.2log.cc

#include"log.h"
#include<iostream>
#include<map>
#include<functional>
#include<time.h>
#include<string.h>

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("<parse_error>", 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("<<error_format %" + 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;
    }
}
}



8.3test.cc

#include <iostream>
#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;
}

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)

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


### 8.3test.cc

```c
#include <iostream>
#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;
}

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)

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

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

相关文章

大华智慧园区管理平台任意密码读取漏洞 复现

文章目录 大华智慧园区管理平台任意密码读取漏洞 复现0x01 前言0x02 漏洞描述0x03 影响平台0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 大华智慧园区管理平台任意密码读取漏洞 复现 0x01 前言 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&…

shell定时清理日志文件、及crontab说明

服务器日志文件一般是每天一个或多个文件&#xff0c;如果日志文件不清理&#xff0c;时间久了就会将磁盘空间占满&#xff0c;从而影响系统的正常运行。 1、分析磁盘空间占用情况 1.1、df 命令 df 命令以磁盘分区为单位查看文件系统中磁盘空间的使用情况。 语法&#xff1a;…

python基础教程:类class

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 class 定义一个类 class 定义一个类, 后面的类别首字母推荐以 大写 的形式定义&#xff0c;比如Calculator. 冒号不能缺 class可以先定义自己的属性&#xff0c…

Java 面向对象的三大特性

面向对象编程有三大特征: 封装、继承和多态。 1.封装 1&#xff09;封装介绍 封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起数据被保护在内部.程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。 2&#xff09;封装的理解和好处 隐…

Windows安装Node.js

1、Node.js介绍 ①、Node.js简介 Node.js是一个开源的、跨平台的JavaScript运行环境&#xff0c;它允许开发者使用JavaScript语言来构建高性能的网络应用程序和服务器端应用。Node.js的核心特点包括&#xff1a; 1. 事件驱动: Node.js采用了事件驱动的编程模型&#xff0c;通…

美股游戏股分析:微软收购游戏公司动视暴雪将迎来一个重要里程碑

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 总结&#xff1a; &#xff08;1&#xff09;微软(MSFT)收购动视暴雪(ATVI)的交易在做出重大让步后目前已经获得了欧盟和美国的监管批准。 &#xff08;2&#xff09;英国英国竞争和市场管理局(CMA)最初对微软收购动视暴雪…

SpringCloud Alibaba - 分布式事务理论(CAP 定理 和 BASE 理论)

目录 一、分布式事务理论 1.1、分布式事务问题 1.2、什么是分布式事务 1.3、解决分布式事务思路 1.3.1、CAP 定理 a&#xff09;Consistency&#xff08;一致性&#xff09; b&#xff09;Availability&#xff08;可用性&#xff09; c&#xff09;Partition tolerance…

反向生成:根据mybatis生成的Class实体类对象反向生成建表语句

微信公众号&#xff1a;大数据高性能计算 针对数据库不小心被删除的情况&#xff0c;以及需要逆向查看某个开源项目数据库表的情况时&#xff0c;往往需要逆向工程。 在这里我们假设 id 字段为主键。在生成 SQL 建表语句时&#xff0c;我们添加了 PRIMARY KEY (id) 来定义 i…

温控仪工作原理

温控仪的工作原理&#xff0c;是通过温度探头或热电偶反馈的电信号&#xff0c;温控仪将得到的电信号转化成温度值&#xff0c;根据设定的温度值&#xff0c;控制加热器的接通和断开来达到控制温度范围的&#xff1a; 1、温度探头或热电偶受温度变化时&#xff0c;会产生微弱的…

CDN技术与企业网站建设:解决疑问与明智选择

介绍CDN技术与疑问 在数字时代&#xff0c;网站是企业线上存在的门户&#xff0c;因此网站的性能和安全至关重要。为了解决一些与网站建设和性能相关的疑问&#xff0c;让我们首先来了解CDN技术。 什么是CDN技术&#xff1f; CDN&#xff0c;即内容分发网络&#xff08;Conte…

Langchain-Chatchat项目:1.2-Baichuan2项目整体介绍

由百川智能推出的新一代开源大语言模型&#xff0c;采用2.6万亿Tokens的高质量语料训练&#xff0c;在多个权威的中文、英文和多语言的通用、领域benchmark上取得同尺寸最佳的效果&#xff0c;发布包含有7B、13B的Base和经过PPO训练的Chat版本&#xff0c;并提供了Chat版本的4b…

【重拾C语言】六、批量数据组织(四)线性表—栈和队列

目录 前言 六、批量数据组织——数组 6.1~3 数组基础知识 6.4 线性表——分类与检索 6.5~7 数组初值&#xff1b;字符串、字符数组、字符串数组&#xff1b;类型定义 typedef 6.8 线性表—栈和队列 6.8.1 栈&#xff08;Stack&#xff09; 全局变量 isEmpty() isFull…

TCP原理特性详解

文章目录 可靠传输机制1.确认应答2.超时重传2.连接管理1.三次握手2.四次挥手 传输效率1.滑动窗口2.流量控制3.拥塞控制4.延时应答5.捎带应答 面向字节流粘包问题 TCP异常情况 可靠传输机制 可靠性&#xff1a;即发送方知道数据是发送成功了&#xff0c;还是失败了。 1.确认应答…

Github-使用2FA验证:使用python实现TOTP验证,python实现github的2FA验证

github新增了2FA验证了&#xff0c; 1、扫描二维码&#xff0c;获取对应字符串 或点击setup key ,获取字符串 2、使用python来生成校验码 安装&#xff1a; pip install pytop import pyotp key XKDRR4WH3LY2WXPH print(pyotp.TOTP(key).now()) 3、将生成的6个验证码&…

第一章 概述 | 计算机网络(谢希仁 第八版)

文章目录 第一章 概述重要内容计算机网络的一些相关知识互联网概述因特网的标准化工作互联网的组成计算机网络的类别计算机网络的性能指标计算机网络的体系结构——-分层次的体系结构 第一章 概述 重要内容 互联网边缘部分和核心部分的作用&#xff0c;以及分组交换的概念 计…

学习记忆——宫殿篇——记忆宫殿——记忆桩——身体——记忆星座

我们在与人攀谈的时候&#xff0c;可以从以下几个维度入手&#xff0c;如&#xff1a;年龄、星座、爱好、工作等。 两点记忆的技巧以及一点知识延伸 两点记忆技巧&#xff1a; 1、第一次见面时要创建回忆线索 2、脑中回忆交流画面&#xff0c;加深线索 一点知识延伸&#xff1…

NoSQL之Redis 主从复制配置详解及哨兵模式

目录 1 Redis 主从复制 1.1 主从复制的作用 1.2 主从复制流程 2 搭建Redis 主从复制 2.1 安装 Redis 2.2 修改 Redis 配置文件&#xff08;Master节点操作&#xff09; 2.3 修改 Redis 配置文件&#xff08;Slave节点操作&#xff09; 2.4 验证主从效果 3 Redis 哨兵模…

自动拟人对话机器人在客户服务方面起了什么作用?

在当今数字时代&#xff0c;企业不断寻求创新的方法来提升客户服务体验。随着科技的不断进步和消费者期望的提升&#xff0c;传统的客户服务方式逐渐无法满足现代消费者的需求。因此&#xff0c;许多企业正在积极探索利用新兴技术来改进客户服务&#xff0c;自动拟人对话机器人…

javaScript关于闭包的理解

首先在了解闭包之前&#xff0c;我们要了解一下环境和作用域 1.环境和作用域 日常生活中我们生活的环境由周边建设如公园&#xff0c;小区&#xff0c;超市构成的。这就构成了环境 在计算机当中环境就是一块内存的数据。 环境是有作用范围的&#xff0c;eg:武汉周边的建设一…

YOLOv8 Tensorrt部署详细介绍(小白从0到1,不会你砍我)

下载YOLOv8项目和Tensorrt部署项目 git clone https://github.com/xiaocao-tian/yolov8_tensorrt.git git clone https://github.com/ultralytics/ultralytics.git 下载yolov8s模型 在YOLOv8项目中新建weights文件夹&#xff0c;将yolov8s.pt放入 运行tensorrt项目中gen_wts…