sylar:日志管理

参照 log4j 先写一个日志系统

以下代码均在同一文件sylar/log.h

开头两行:

cpp 复制代码
#ifndef __SYLAR_LOG_H__
#define __SYLAR_LOG_H__



#endif

#ifndef 是 "if not defined" 的缩写,它是一个预处理指令,去检查在当前的编译阶段,SYLAR_LOG_H 这个标识符是否还没有被定义过。如果没有被定义,那么后续在 #ifndef 和对应的 #endif 之间的代码将会被正常编译处理;反之,如果该标识符已经被定义了,那么这中间的代码将会被预处理器跳过,不会参与编译。

109 - 142 LogLevel

日志级别枚举

cpp 复制代码
enum Level {
    UNKNOW = 0,   // 未知级别
    DEBUG = 1,    // 调试级别
    INFO = 2,     // 信息级别
    WARN = 3,     // 警告级别
    ERROR = 4,    // 错误级别
    FATAL = 5     // 致命错误级别
};

ToString 方法

cpp 复制代码
static const char* ToString(LogLevel::Level level);

具体实现:

cpp 复制代码
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 "UNKNOW";
    }
    return "UNKNOW";
}

宏定义:

XX(name) 接受一个参数 name,然后生成一个 switch 的 case 语句。

#name 是一个 预处理器字符串化操作,它会将宏参数 name 转换为字符串(例如:name = DEBUG 时,#name 会变为 "DEBUG")。

如果 level 不匹配任何一个已知的日志级别),switch 语句会进入 default 分支,返回字符串 "UNKNOW"

具体就是通过宏减少了代码量

FromString 方法

cpp 复制代码
static LogLevel::Level FromString(const std::string& str);

具体实现:

cpp 复制代码
LogLevel::Level LogLevel::FromString(const std::string& str) {
#define XX(level, v) \
    if(str == #v) { \
        return LogLevel::level; \
    }
    XX(DEBUG, debug);
    XX(INFO, info);
    XX(WARN, warn);
    XX(ERROR, error);
    XX(FATAL, fatal);

    XX(DEBUG, DEBUG);
    XX(INFO, INFO);
    XX(WARN, WARN);
    XX(ERROR, ERROR);
    XX(FATAL, FATAL);
    return LogLevel::UNKNOW;
#undef XX
}

通过使用宏,代码实现变得非常简洁。每一个日志级别的匹配判断都由宏来生成,这样就避免了手动编写多个重复的 if 判断语句。代码的可维护性和可扩展性也得到了提升。

如果需要添加更多的日志级别,只需要在 XX 宏调用处添加新的日志级别,而不需要修改整个函数的结构。

支持了字符串(小写和大写)到日志级别枚举值的转换。

宏和函数定义总结

宏的基本特点

宏是由 预处理器 在编译之前展开的,它通过文本替换来处理代码。宏通常使用 #define 来定义。

优点:

  • 性能:宏在编译前直接替换代码,因此 没有函数调用的开销。这在某些性能关键的代码中非常有用,比如需要大量重复计算的常量表达式。
  • 灵活性:宏可以接受任意复杂的参数,并通过字符串化 (#) 或拼接 (##) 来动态生成代码。

缺点:

  • 调试困难:宏没有类型检查,它们在预处理阶段展开,调试时你无法看到宏展开后的结果。如果宏中存在错误,编译器可能会提示不明确的错误信息。
    可读性差:宏的展开过程是自动进行的,可能导致代码的可读性和可维护性差,尤其是复杂的宏定义。
  • 无法进行类型检查:宏没有类型信息,它们只是简单的文本替换,因此很容易出现错误(例如,传递了错误类型的参数)。
  • 作用域问题:宏是全局的,没有作用域限制,可能会无意中覆盖现有变量或导致名字冲突。

147 - 252 LogEvent

主要用于表示一次日志事件(日志条目)。每个日志事件包含了丰富的上下文信息,如日志的来源文件、行号、线程信息、日志级别、日志内容等。LogEvent 类是日志系统中非常关键的一个部分,它提供了日志记录所需的所有元数据。

类的构造

cpp 复制代码
LogEvent(std::shared_ptr<Logger> logger, LogLevel::Level level
       ,const char* file, int32_t line, uint32_t elapse
       ,uint32_t thread_id, uint32_t fiber_id, uint64_t time
       ,const std::string& thread_name);
  • std::shared_ptr<Logger> logger:指向日志器对象的智能指针,表示记录该日志事件的日志器。一个日志事件必须通过某个日志器来输出。

  • LogLevel::Level level:日志级别,表示此次日志事件的严重程度。

  • const char* file:触发日志事件的源文件的文件名。

  • int32_t line:触发日志事件的源文件中的行号。

  • uint32_t elapse:程序启动到当前日志事件发生时的时间差(以毫秒为单位)。这可以帮助追踪程序运行的时间。

  • uint32_t thread_id:生成该日志事件的线程 ID。

  • uint32_t fiber_id:生成该日志事件的协程 ID。这个值适用于多协程的程序,有助于区分是哪个协程产生的日志。

  • uint64_t time:日志事件生成的时间戳(通常以秒为单位)。

  • const std::string& thread_name:生成该日志事件的线程名称。

成员函数列表

cpp 复制代码
getFile():返回触发日志事件的源文件的文件名。
getLine():返回触发日志事件的源文件中的行号。
getElapse():返回程序启动后到日志事件发生的毫秒数。
getThreadId():返回生成该日志事件的线程 ID。
getFiberId():返回生成该日志事件的协程 ID。
getTime():返回该日志事件的时间戳(秒)。
getThreadName():返回生成该日志事件的线程名称。
getContent():返回日志内容,即 std::stringstream 中的字符串内容。这是日志的主要信息部分。
getLogger():返回指向日志器对象的智能指针,表示生成该日志事件的日志器。
getLevel():返回日志事件的日志级别。
getSS():返回 std::stringstream 对象的引用,允许将日志内容写入该流中。

11个成员函数,对应10个成员变量,其中m_ss内容流对应两个成员函数:

分别返回m_ss和m_ss.str()

cpp 复制代码
  std::string getContent() const { return m_ss.str();}
  std::stringstream& getSS() { return m_ss;}

格式化函数

format(const char* fmt, ...)这是一个变参函数,用于将格式化后的字符串内容写入到 m_ss 中。它使用 printf 风格的格式化字符串。

format(const char* fmt, va_list al):这是一个接收 va_list 的版本,用于处理 format 函数中的可变参数列表,支持更灵活的格式化输出。

使用场景

LogEvent 类通常由 Logger 类在日志记录时生成,并提供给 Appender 类用于输出。它通过包含丰富的上下文信息(如文件、行号、线程、协程等),使得开发者可以在分析日志时,快速定位问题。

257 - 285LogEventWrap 日志事件包装器

LogEventWrap 类的目的是对日志事件进行封装,方便在其他地方处理日志事件的相关内容。通过它,可以获取到日志事件对象和日志内容流。

成员变量

cpp 复制代码
LogEvent::ptr m_event;

m_event 是 LogEvent::ptr 类型的成员变量,存储了实际的日志事件。通过这个成员,LogEventWrap 类可以持有并操作一个日志事件对象

类的构造和析构

cpp 复制代码
LogEventWrap(LogEvent::ptr e);
~LogEventWrap();
cpp 复制代码
LogEventWrap::LogEventWrap(LogEvent::ptr e)
    :m_event(e) {
}

LogEventWrap::~LogEventWrap() {
    m_event->getLogger()->log(m_event->getLevel(), m_event);
}

析构里:调用getLogger()返回了一个share_ptr < Logger > 日志器

再使用他的log方法传入logLevel,和logevent

具体的Logger类会说

成员函数

cpp 复制代码
LogEvent::ptr getEvent() const { return m_event; }
cpp 复制代码
std::stringstream& LogEventWrap::getSS() {
    return m_event->getSS();
}

288 - 366 LogFormatter 日志格式化

成员变量

cpp 复制代码
std::string m_pattern
保存日志格式模板(例如:
%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n)。

std::vector<FormatItem::ptr> m_items:
保存解析后的格式化项对象,每个对象对应模板中的一个占位符
(如 %d、%p 等)。

bool m_error:
标识日志格式是否出现错误。
如果 m_error 为 true,表示格式解析过程中发生了错误。

typedef std::shared_ptr<LogFormatter> ptr;

构造函数

cpp 复制代码
传入字符串参数
	LogFormatter::LogFormatter(const std::string& pattern)
    :m_pattern(pattern) {
    init();
}    
init()函数
cpp 复制代码
// 初始化函数,用于解析日志格式模板并生成相应的格式化项
void LogFormatter::init() {
    // 用于存储格式化项的临时数据(包括字符串部分、格式化部分和标记)
    std::vector<std::tuple<std::string, std::string, int>> vec;
    std::string nstr;  // 临时存储普通字符串部分

    // 遍历日志格式模板
    for (size_t i = 0; i < m_pattern.size(); ++i) {
        // 如果当前字符不是 '%',说明它是普通字符,直接加入到 nstr 中
        if (m_pattern[i] != '%') {
            nstr.append(1, m_pattern[i]);
            continue;
        }

        // 如果遇到连续的 '%%',将 '%' 添加到 nstr 中,跳过下一个字符
        if ((i + 1) < m_pattern.size()) {
            if (m_pattern[i + 1] == '%') {
                nstr.append(1, '%');
                continue;
            }
        }

        // 解析格式化项
        size_t n = i + 1;  // 从 '%' 后的下一个字符开始解析
        int fmt_status = 0;  // 0: 解析格式标识符, 1: 解析格式内容
        size_t fmt_begin = 0;  // 格式内容的起始位置
        std::string str;  // 存储格式标识符
        std::string fmt;  // 存储格式内容

        // 遍历模板中的每个字符,直到格式项解析完毕
        while (n < m_pattern.size()) {
            // 如果当前字符既不是字母也不是 '{' 或 '}',说明已解析完格式标识符
            if (!fmt_status && (!isalpha(m_pattern[n]) && m_pattern[n] != '{' && m_pattern[n] != '}')) {
                str = m_pattern.substr(i + 1, n - i - 1);  // 提取格式标识符
                break;
            }
            if (fmt_status == 0) {
                // 如果是 '{',说明是带格式内容的格式化项,进入格式化内容解析状态
                if (m_pattern[n] == '{') {
                    str = m_pattern.substr(i + 1, n - i - 1);  // 提取格式标识符
                    fmt_status = 1;  // 进入格式化内容解析
                    fmt_begin = n;  // 标记格式开始位置
                    ++n;  // 跳过 '{'
                    continue;
                }
            } else if (fmt_status == 1) {
                // 如果是 '}',则结束格式内容的解析
                if (m_pattern[n] == '}') {
                    fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1);  // 提取格式内容
                    fmt_status = 0;  // 格式内容解析结束
                    ++n;  // 跳过 '}'
                    break;
                }
            }
            ++n;  // 移动到下一个字符
            if (n == m_pattern.size()) {
                // 如果解析到字符串结尾,且没有找到 '}', 说明格式项不完整
                if (str.empty()) {
                    str = m_pattern.substr(i + 1);  // 提取剩余部分
                }
            }
        }

        // 如果格式项解析完成,将其存储到 vec 中
        if (fmt_status == 0) {
            if (!nstr.empty()) {
                vec.push_back(std::make_tuple(nstr, std::string(), 0));  // 存储普通字符串部分
                nstr.clear();
            }
            vec.push_back(std::make_tuple(str, fmt, 1));  // 存储格式化项
            i = n - 1;  // 更新 i 为格式项解析结束的位置
        } else if (fmt_status == 1) {
            // 如果格式项解析失败(例如没有找到对应的 '}'),记录错误
            std::cout << "pattern parse error: " << m_pattern << " - " << m_pattern.substr(i) << std::endl;
            m_error = true;
            vec.push_back(std::make_tuple("<<pattern_error>>", fmt, 0));  // 存储错误项
        }
    }

    // 如果 nstr 中还有剩余的普通字符串,添加到 vec 中
    if (!nstr.empty()) {
        vec.push_back(std::make_tuple(nstr, "", 0));
    }

    // 定义一个静态映射表,将格式标识符(如 'm'、'p' 等)映射到相应的 FormatItem 类型
    static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)>> s_format_items = {
#define XX(str, C) \
        {#str, [](const std::string& fmt) { return FormatItem::ptr(new C(fmt));}}

        XX(m, MessageFormatItem),           // m: 消息
        XX(p, LevelFormatItem),             // p: 日志级别
        XX(r, ElapseFormatItem),            // r: 累计毫秒数
        XX(c, NameFormatItem),              // c: 日志名称
        XX(t, ThreadIdFormatItem),          // t: 线程 ID
        XX(n, NewLineFormatItem),           // n: 换行符
        XX(d, DateTimeFormatItem),          // d: 时间
        XX(f, FilenameFormatItem),          // f: 文件名
        XX(l, LineFormatItem),              // l: 行号
        XX(T, TabFormatItem),               // T: 制表符
        XX(F, FiberIdFormatItem),           // F: 协程 ID
        XX(N, ThreadNameFormatItem),        // N: 线程名称
#undef XX
    };

    // 遍历 vec,根据格式标识符创建相应的 FormatItem 对象,并加入 m_items 容器
    for (auto& i : vec) {
        if (std::get<2>(i) == 0) {
            // 如果是普通字符串部分,创建 StringFormatItem 对象
            m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(i))));
        } else {
            // 如果是格式化项,查找对应的 FormatItem 类型
            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) + ">>")));
                m_error = true;
            } else {
                // 创建对应的 FormatItem 对象并传入格式内容
                m_items.push_back(it->second(std::get<1>(i)));
            }
        }
    }
}

成员函数format

cpp 复制代码
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();
}

std::ostream& LogFormatter::format(std::ostream& ofs, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
    for(auto& i : m_items) {
        i->format(ofs, logger, level, event);
    }
    return ofs;
}

这两个 format 函数分别用于将日志事件格式化为字符串或输出流(std::ostream)格式。它们的核心逻辑非常相似,主要依赖于 m_items 容器中的格式化项(FormatItem)来逐步构建最终的日志信息。

返回string类型的是给后面StdoutLogAppender用

返回ostream类型的是给FileLogAppender用

i是m_items里的,是嵌套类FormatItem对象

他有format函数

cpp 复制代码
    virtual void format(std::ostream& os, 
    std::shared_ptr<Logger> logger, LogLevel::Level level, 
    LogEvent::ptr event) = 0;

内部类FormatItem

FormatItem内部类用于封装日志内容项格式化的相关逻辑,使得对每一项具体的格式化操作能够独立进行处理。不同的日志内容项(比如消息、日志级别、时间等)都有各自的格式化要求和实现方式,通过将它们抽象成一个个FormatItem对象,

cpp 复制代码
 virtual void format(std::ostream& os, std::shared_ptr
 <Logger> logger, LogLevel::Level level,
  LogEvent::ptr event) = 0;

提供用于继承的虚函数

428 - 541 Logger 日志器

成员变量

cpp 复制代码
m_name用于存储日志器的名称,方便对不同日志器进行区分和标识。
m_level记录当前日志器的日志级别,决定了哪些级别的日志可以被该日志器记录。
m_mutex是前面定义的Spinlock类型的锁,用于在多线程环境下保护类中共享资源(如m_appenders、m_formatter等)的并发访问安全。
m_appenders是一个存储LogAppender智能指针的链表,用于管理该日志器关联的所有日志目标对象。
m_formatter是指向日志格式器的智能指针,负责控制日志的输出格式。
m_root是指向主日志器的智能指针,可能在日志系统的层次结构或者一些特殊的管理逻辑中起到关联、继承等相关作用,具体依赖于整个日志系统的设计。

构造函数

cpp 复制代码
Logger::Logger(const std::string& name)
    :m_name(name)
    ,m_level(LogLevel::DEBUG) {
    m_formatter.reset(new LogFormatter("%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"));
}

log方法实现

cpp 复制代码
/**
 * 记录日志事件
 * @param level 日志级别
 * @param event 日志事件
 */
void Logger::log(LogLevel::Level level, LogEvent::ptr event) {
    // 如果日志级别大于等于当前日志器的级别
    if(level >= m_level) {
        // 获取当前日志器的共享指针
        auto self = shared_from_this();
        // 加锁,保护日志器的互斥量
        MutexType::Lock lock(m_mutex);
        // 如果有日志输出目标
        if(!m_appenders.empty()) {
            // 遍历所有日志输出目标
            for(auto& i : m_appenders) {
                // 调用每个目标的 log 方法,记录日志事件
                i->log(self, level, event);
            }
        // 如果没有日志输出目标,但存在根日志器
        } else if(m_root) {
            // 调用根日志器的 log 方法,记录日志事件
            m_root->log(level, event);
        }
    }
}

371 - 423 LogAppender 日志输出目标

定义了一个 LogAppender 类,是一个基类

子类StdoutLogAppender和FileLogAppender继承它

提供给子类继承的方法

cpp 复制代码
	virtual std::string toYamlString() = 0;
    virtual void log(std::shared_ptr<Logger> logger, 
    LogLevel::Level level, LogEvent::ptr event) = 0;

这两个是不同子类之间有区分,所以要虚函数

对于子类没区别的

cpp 复制代码
    /**
     * @brief 更改日志格式器
     */
    void setFormatter(LogFormatter::ptr val);

    /**
     * @brief 获取日志格式器
     */
    LogFormatter::ptr getFormatter();

    /**
     * @brief 获取日志级别
     */
    LogLevel::Level getLevel() const { return m_level;}

    /**
     * @brief 设置日志级别
     */
    void setLevel(LogLevel::Level val) { m_level = val;}

其中,后两个setLevel和getLevel 仅仅是对成员变量 m_level 进行读取或修改,因此它们可以直接在类的定义中实现。

而前面两个需要对m_formatter 和 m_hasFormatter 成员的访问是线程安全的。

546 - 551 StdoutLogAppender 输出到控制台的Appender

cpp 复制代码
class StdoutLogAppender : public LogAppender {
public:
    typedef std::shared_ptr<StdoutLogAppender> ptr;
    void log(Logger::ptr logger, LogLevel::Level level, 
    LogEvent::ptr event) override;
    std::string toYamlString() override;
};

对log和toYamlString进行了重写

默认构造

cpp 复制代码
std::string StdoutLogAppender::toYamlString() {
    MutexType::Lock lock(m_mutex);  // 1. 锁定互斥量,保证线程安全
    YAML::Node node;               // 2. 创建一个 YAML 节点对象
    
    node["type"] = "StdoutLogAppender";  // 3. 设置输出目标的类型
    
    // 4. 如果日志级别不是 UNKNOW,加入 level 字段
    if (m_level != LogLevel::UNKNOW) {
        node["level"] = LogLevel::ToString(m_level);  // 将日志级别转换为字符串并设置
    }

    // 5. 如果存在格式器并且格式器有效,加入 formatter 字段
    if (m_hasFormatter && m_formatter) {
        node["formatter"] = m_formatter->getPattern();  // 获取并设置格式器的模式(模板)
    }

    // 6. 将 YAML 节点对象输出到字符串流中
    std::stringstream ss;
    ss << node;  // 将 YAML 节点序列化为字符串流内容

    return ss.str();  // 7. 返回 YAML 字符串
}

得到日志输出的字符串

cpp 复制代码
void StdoutLogAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
    if(level >= m_level) {  // 1. 检查日志级别
        MutexType::Lock lock(m_mutex);  // 2. 锁定互斥量,确保线程安全
        m_formatter->format(std::cout, logger, level, event);  // 3. 格式化日志并输出到控制台
    }
}

其中format是这样的:
std::ostream& LogFormatter::format(std::ostream& ofs, 
std::shared_ptr<Logger> logger, LogLevel::Level level, 
LogEvent::ptr event) {
    for(auto& i : m_items) {
        i->format(ofs, logger, level, event);
    }
    return ofs;
}

556 - 575 FileLogAppender输出到文件的Appender

cpp 复制代码
lass 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;
    std::string toYamlString() override;

    /**
     * @brief 重新打开日志文件
     * @return 成功返回true
     */
    bool reopen();
private:
    /// 文件路径
    std::string m_filename;
    /// 文件流
    std::ofstream m_filestream;
    /// 上次重新打开时间
    uint64_t m_lastTime = 0;
};

由于是文件,添加了文件打开时间操作

也有构造函数,因为要传入文件名

580 - 615 LoggerManager 日志器管理类

给logger设置根日志器:

cpp 复制代码
LoggerManager::LoggerManager() {
    m_root.reset(new Logger);
    m_root->addAppender(LogAppender::ptr(new StdoutLogAppender));

    m_loggers[m_root->m_name] = m_root;

    init();
}

在日志器管理器中寻找文件名是name的日志器

cpp 复制代码
Logger::ptr LoggerManager::getLogger(const std::string& name) {
    MutexType::Lock lock(m_mutex);
    auto it = m_loggers.find(name);
    if(it != m_loggers.end()) {
        return it->second;
    }

    Logger::ptr logger(new Logger(name));
    logger->m_root = m_root;
    m_loggers[name] = logger;
    return logger;
}

返回根目录器

cpp 复制代码
    Logger::ptr getRoot() const { return m_root;}
cpp 复制代码
std::string LoggerManager::toYamlString() {
    MutexType::Lock lock(m_mutex);
    YAML::Node node;
    for(auto& i : m_loggers) {
        node.push_back(YAML::Load(i.second->toYamlString()));
    }
    std::stringstream ss;
    ss << node;
    return ss.str();
}

这个函数的目的是将 LoggerManager 管理的所有日志器的配置信息以 YAML 格式输出,以便于查看和管理

25-100 宏定义

cpp 复制代码
#define SYLAR_LOG_LEVEL(logger, level) \
    if(logger->getLevel() <= level) \
        sylar::LogEventWrap(sylar::LogEvent::ptr(new 
        sylar::LogEvent(logger, level, \
                        __FILE__, __LINE__, 0, 
                        sylar::GetThreadId(),\
                sylar::GetFiberId(), time(0), 
                sylar::Thread::GetName()))).getSS()

定义宏SYLAR_LOG_LEVEL

如果logger->getLevel() <= level就把宏替换成

调用

cpp 复制代码
LogEventWrap::LogEventWrap(LogEvent::ptr e)
    :m_event(e) {
}

的构造函数,

该构造要传入,LogEvent的指针,该指针由

cpp 复制代码
sylar::LogEvent::ptr(new sylar::LogEvent(logger, level, \
                        __FILE__, __LINE__, 0, 
                        sylar::GetThreadId(),\
                sylar::GetFiberId(), time(0), 
                sylar::Thread::GetName()))).getSS()

new 出来,赋值给一个std::shared_ptr< LogEvent >

而new需要调用构造函数

cpp 复制代码
LogEvent::LogEvent(std::shared_ptr<Logger> logger, LogLevel::Level level
            ,const char* file, int32_t line, uint32_t elapse
            ,uint32_t thread_id, uint32_t fiber_id, uint64_t time
            ,const std::string& thread_name)
    :m_file(file)
    ,m_line(line)
    ,m_elapse(elapse)
    ,m_threadId(thread_id)
    ,m_fiberId(fiber_id)
    ,m_time(time)
    ,m_threadName(thread_name)
    ,m_logger(logger)
    ,m_level(level) {
}

其中

cpp 复制代码
pid_t GetThreadId() {
    return syscall(SYS_gettid);
}

uint32_t GetFiberId() {
    return sylar::Fiber::GetFiberId();
}

const std::string& Thread::GetName() {
    return t_thread_name;
}

std::stringstream& LogEventWrap::getSS() {
    return m_event->getSS();
}

// m_event->getSS()
    std::stringstream& getSS() { return m_ss;}
相关推荐
AskHarries9 分钟前
做国内还是出海
后端
云泽80814 分钟前
笔试算法 - 链表篇(一):移除、反转、合并、回文判断全解析
数据结构·c++·算法·链表
小poop17 分钟前
深入理解指针(中):数组与指针的进阶之旅
c++
z2005093019 分钟前
【Linux学习】Linux中的进程程序替换
linux·服务器·学习
YikNjy21 分钟前
break和continue
java·开发语言·算法
日月云棠34 分钟前
10 Integer —— 最常用的整数包装类深度解析
java·后端
大鸡腿同学38 分钟前
大模型为何总 “胡说八道”?做完 RAG 知识库,我看懂了它的底层逻辑
后端
秋938 分钟前
java项目中cpu飙升排查及解决方法
java·开发语言
野生技术架构师39 分钟前
牛客网2026最新大厂Java高频面试题精选(附标准答案)
java·开发语言
PH = 742 分钟前
JAVA的SPI机制
java·开发语言