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;}
相关推荐
禁默1 分钟前
AWT 布局管理器:Java 图形界面编程的核心
java·开发语言
潜洋1 小时前
Spring Boot教程之二十五: 使用 Tomcat 部署项目
java·spring boot·后端·tomcat
艳阳天_.1 小时前
excel 列名是数据表 的字段名 ,单元格的值 是数据表对应字段的值,生成sql插入语句
java·服务器·数据库
向宇it2 小时前
【从零开始入门unity游戏开发之——C#篇13】命名规范——驼峰命名法和帕斯卡命名法,函数(方法)的使用介绍
java·开发语言·游戏·unity·c#·游戏引擎
Forworder2 小时前
MVC前后端交互案例--留言板
java·开发语言·java-ee·mvc·intellij-idea·交互·postman
鱼香鱼香rose2 小时前
面经zijie
java·开发语言
TianyaOAO2 小时前
lvs介绍和DR模式
服务器·网络·lvs
观音山保我别报错2 小时前
JavaSe部分总结
java·开发语言
TechPioneer_lp4 小时前
腾讯技术岗位笔试&面试题(三)
c++·笔记·面试·软件工程·个人开发
无职转生真好看4 小时前
C++:异常(上)
c++