【sylar-webserver】1 日志系统

整体设计

LogLevel - LogEvent

LogLevel 类内枚举日志级别 LogEvent 一个时间 log 需要输出的成员变量

cpp 复制代码
class LogLevel{
    public:
        enum Level{
            UNKNOW = 0,
            DEBUG = 1,
            INFO = 2,
            WARN = 3,
            ERROR = 4,
            FATAL = 5,
            NOTEST = 6,    
        };

        static const char* ToString(LogLevel::Level level);     // 枚举转字符表示
        static LogLevel::Level FromString(const std::string& str);    // 字符串转枚举表达
};

//日志事件
class LogEvent{
    public:
        typedef std::shared_ptr<LogEvent> ptr;      //使用ptr,就避免了对象的复制和拷贝
        LogEvent(const char* file, int32_t line, uint32_t elapse, uint32_t threadId, const std::string& threadName, uint32_t fiberId , uint64_t time, LogLevel::Level level);
         
        const char* getFile() const {return m_file;}
        int32_t getLine() const {return m_line;}
        uint32_t getElapse() const {return m_elapse;}
        uint32_t getThreadId() const {return m_threadId;}
        std::string getThreadName() const {return m_threadName;}
        uint32_t getFiberId() const {return m_fiberId;}
        uint64_t getTime() const {return m_time;}
        std::string getContent() const {return m_ss.str();}
        LogLevel::Level getLevel() const {return m_level;}

        std::stringstream& getSS() {return m_ss;}

        void format(const char* fmt, ...);
        void format(const char* fmt, va_list al);

    private:
        const char* m_file = nullptr;   //文件名
        int32_t m_line = 0;             //行号
        uint32_t m_elapse = 0;          //程序启动到现在的毫秒数
        uint32_t m_threadId = 0;        //线程
        std::string m_threadName;       //线程名
        uint32_t m_fiberId = 0;         //协程
        uint64_t m_time = 0;            //时间戳
        std::stringstream m_ss;
        LogLevel::Level m_level;        //日志级别
};

<< 流式输出

getSS() 得到 stringstream,使得通过 << 输入日志信息。

printf 格式输出

cpp 复制代码
void LogEvent::format(const char* fmt, ...){
    va_list al;
    va_start(al, fmt);
    format(fmt, al);
    va_end(al);
}

void LogEvent::format(const char* fmt, va_list al){
    char* buf = nullptr;
    int len = vasprintf(&buf, fmt, al);
    if(len != -1){
        m_ss << std::string(buf, len);
        free(buf);
    }
}

LogFormatter - FormatterItem

cpp 复制代码
//日志格式器
class LogFormatter{
    public:
        typedef std::shared_ptr<LogFormatter> ptr;
        /**
         * @brief 构造函数
         * @param[in] pattern 格式模板,参考sylar与log4cpp
         * @details 模板参数说明:
         * - %%m 消息
         * - %%p 日志级别
         * - %%c 日志器名称
         * - %%d 日期时间,后面可跟一对括号指定时间格式,比如%%d{%%Y-%%m-%%d %%H:%%M:%%S},这里的格式字符与C语言strftime一致
         * - %%r 该日志器创建后的累计运行毫秒数
         * - %%f 文件名
         * - %%l 行号
         * - %%t 线程id
         * - %%F 协程id
         * - %%N 线程名称
         * - %%% 百分号
         * - %%T 制表符
         * - %%n 换行
         * 
         * 默认格式:%%d{%%Y-%%m-%%d %%H:%%M:%%S}%%T%%t%%T%%N%%T%%F%%T[%%p]%%T[%%c]%%T%%f:%%l%%T%%m%%n
         * 
         * 默认格式描述:年-月-日 时:分:秒 [累计运行毫秒数] \\t 线程id \\t 线程名称 \\t 协程id \\t [日志级别] \\t [日志器名称] \\t 文件名:行号 \\t 日志消息 换行符
         */
        LogFormatter(const std::string& 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::string format(std::shared_ptr<Logger> logger, LogEvent::ptr event);
        void init();                                // 解析模板字符串
        bool isError() const {return m_error;}
        std::string getPattern(){return m_pattern;}
    public:
        class FormatterItem{
            public:
                typedef std::shared_ptr<FormatterItem> ptr;
                virtual ~FormatterItem(){}
                virtual void format(std::ostream& os, std::shared_ptr<Logger> logger ,LogEvent::ptr event) = 0;
        };

    private:
        std::string m_pattern;                      // 模板字符串 "%d{%Y-%m-%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
        std::vector<FormatterItem::ptr> m_items;    // 存储每一个日志类型的输出子类
        bool m_error = false;
};

LogFormatter 定义了模板字符串 m_pattern,以及每一种格式的 FormatterItem 子类集合 m_items(通过解析这个字符串,得到存储每一个格式输出子类)

状态机解析⭐⭐⭐⭐⭐

LogFormatter 在 init(),状态机 解析 m_pattern。

cpp 复制代码
// 初始化日志格式解析器
void LogFormatter::init() {
    // 解析模式字符串,示例:"%d{%Y-%m-%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
    
    // 存储解析结果的三元组容器:
    // tuple<原始字符串, 格式参数, 类型> 
    // 类型说明:0-普通字符串,1-格式项
    std::vector<std::tuple<std::string, std::string, int>> vec;
    std::string nstr; // 临时存储普通字符
    bool error = false; // 解析错误标志

    // 遍历模式字符串每个字符
    for(size_t i = 0 ; i < m_pattern.size() ; ++i) {
        // 处理普通字符(非%开头)
        if(m_pattern[i] != '%') {
            nstr.append(1, m_pattern[i]);
            continue;
        }

        // 处理转义字符%%
        if((i+1) < m_pattern.size() && m_pattern[i+1] == '%') {
            nstr.append(1, '%');
            i++; // 跳过下一个%
            continue;
        }

        // 开始解析格式项(如%d{...})
        size_t pos = i + 1;
        int fmt_status = 0;    // 0-寻找格式项,1-解析{}内参数
        size_t fmt_begin = 0;  // {的位置索引
        std::string str;       // 格式项标识符(如d)
        std::string fmt;       // {}内的格式参数(如%Y-%m-%d)

        while(pos < m_pattern.size()) {
            // 状态0:寻找格式项结束或{
            if(fmt_status == 0 && 
              !isalpha(m_pattern[pos]) && // 非字母字符作为分隔,相当于 %f:%l,其中的':'。
              m_pattern[pos] != '{' && 
              m_pattern[pos] != '}') {
                str = m_pattern.substr(i+1, pos-i-1);
                break;
            }
            
            // 遇到{开始解析格式参数
            if(fmt_status == 0 && m_pattern[pos] == '{') {
                str = m_pattern.substr(i+1, pos-i-1);
                fmt_status = 1;
                fmt_begin = pos;
                pos++;
                continue;
            }
            
            // 状态1:寻找}结束
            if(fmt_status == 1) {
                if(m_pattern[pos] == '}') {
                    fmt = m_pattern.substr(fmt_begin+1, pos-fmt_begin-1);
                    fmt_status = 0;
                    pos++;
                    break;
                }
            }
            pos++;
            if(pos == m_pattern.size()){    //如果到了结尾,还是没有str
                if(str.empty()){
                    str = m_pattern.substr(i+1);
                }
            }
        }

        // 处理解析结果
        if(fmt_status == 0) {
            // 保存之前的普通字符串
            if(!nstr.empty()) {
                vec.emplace_back(nstr, "", 0);
                nstr.clear();
            }
            // 保存当前格式项
            vec.emplace_back(str, fmt, 1);
            i = pos - 1; // 跳过已解析部分
        } else {
            // {}不匹配的错误处理
            std::cout << "pattern parse error:" << m_pattern << " - " 
                     << m_pattern.substr(i) << std::endl;
            vec.emplace_back("<<pattern_error>>", fmt, 0);
            error = true;
        }
    }

    // 保存最后的普通字符串
    if(!nstr.empty()) {
        vec.emplace_back(nstr, "", 0);
    }

    // 格式项映射表(关键说明)⭐⭐⭐
    static std::map<std::string, std::function<FormatterItem::ptr(const std::string&)>> s_format_items = {
        // 格式说明:
        // %m 消息内容 | %p 日志级别 | %r 耗时(毫秒)
        // %c 日志器名称 | %t 线程ID | %N 线程名称
        // %F 协程ID | %l 行号 | %d 时间 | %n 换行
        // %f 文件名 | %T 制表符
#define XX(str, C) {#str, [](const std::string& fmt) { return FormatterItem::ptr(new C(fmt)); }}
        XX(m, MessageFormatterItem),    // 消息体
        XX(p, LevelFormatterItem),      // 日志级别
        XX(r, ElapseFormatterItem),     // 程序启动后的耗时
        XX(c, LoggerNameFormatterItem), // 日志器名称
        XX(t, ThreadIdFormatterItem),   // 线程ID
        XX(N, ThreadNameFormatterItem), // 线程名称
        XX(F, FiberIdFormatterItem),    // 协程ID
        XX(l, LineFormatterItem),       // 行号
        XX(d, DataTimeFormatterItem),   // 时间(可带格式参数)
        XX(n, NewLineFormatterItem),    // 换行符
        XX(f, FilenameFormatterItem),   // 文件名
        XX(T, TabFormatterItem)         // 制表符
#undef XX
    };

    // 构建最终的格式化项列表
    for(auto& i : vec) {
        if(std::get<2>(i) == 0) {
            // 普通字符串项
            m_items.push_back(std::make_shared<StringFormatterItem>(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(std::make_shared<StringFormatterItem>(
                    "<<error_format %" + std::get<0>(i) + ">>"));
                error = true;
            } else {
                // 创建对应的格式化项(如时间格式化项)
                m_items.push_back(it->second(std::get<1>(i)));
            }
        }
    }

    // 设置最终错误状态
    if(error) {
        m_error = true;
    }
}

emplace_back (C++11)⭐⭐⭐ 具有push_back()和emplace_back()函数功能的容器:deque、list、vector ● push_back()向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素) ● emplace_back()则直接在容器尾部创建这个元素省去了拷贝或移动元素的过程,所以在效率上更优。

解析完毕后,调用 format。遍历执行 m_items 即可。

cpp 复制代码
std::string LogFormatter::format(std::shared_ptr<Logger> logger, LogEvent::ptr event){
    // TODO
    std::stringstream ss;
    for(auto& it : m_items){
        it->format(ss, logger, event);
    }
    return ss.str();
}

LogAppender

LogAppender 输出器,通过子类StdoutLogAppender,FileLogAppender 继承,实现多种输出方式。 LogAppender 内有 LogFormatter::ptr 成员变量

cpp 复制代码
//日志输出器
class LogAppender{
    public:
        typedef std::shared_ptr<LogAppender> ptr;
        typedef SpinkLock MutexType;

        LogAppender(LogLevel::Level level, LogFormatter::ptr formatter);
        virtual ~LogAppender(){}
        //纯虚函数
        virtual void log(std::shared_ptr<Logger> logger, LogEvent::ptr event) = 0;
        virtual std::string toYamlString() = 0;
        void setLevel(LogLevel::Level level){m_level = level;}
        void setFormatter(LogFormatter::ptr val);
        LogFormatter::ptr getFormatter();
    
    protected:
        LogLevel::Level m_level;
        LogFormatter::ptr m_formatter;      //通过这个类,对日志进行格式化
        MutexType m_mutex;
};

//输出到控制台
class StdoutLogAppender : public LogAppender{
public:
    typedef std::shared_ptr<StdoutLogAppender> ptr;
    StdoutLogAppender(LogLevel::Level level, LogFormatter::ptr formatter);
    std::string toYamlString();
    void log(std::shared_ptr<Logger> logger, LogEvent::ptr event) override;
private:
};

//输出到文件
class FileLogAppender : public LogAppender{
public:
    typedef std::shared_ptr<FileLogAppender> ptr;
    FileLogAppender(const std::string& filename, LogLevel::Level level, LogFormatter::ptr formatter);
    std::string toYamlString();
    void log(std::shared_ptr<Logger> logger, LogEvent::ptr event) override;
    
    // 重新打开文件
    // 如果 当前的日志事件 比 上一个日志事件 超过 interval 秒,就重新打开一次日志文件。把缓存写入。也避免线程间频繁开关 文件。
    // 当 interval = 0时,直接重新打开文件。
    bool reopen(uint64_t now, uint64_t interval = 3);
private:
    std::string m_filename;
    std::ofstream m_filestream;     //#include <fstream>
    bool m_reopenError; 
    uint64_t m_lastTime;              // 文件最近一次打开的事件 事件
};

LogAppender 日志输出其也能控制输出的日志级别(例如,实现不同级别的日志输出到不同文件。) 日志输出器 存在 重构的空间 ⭐⭐⭐(例如,基于异步日志系统)

Logger

Logger 日志器 Logger 内有 std::listLogAppender::ptrm_appenders 成员变量 存储所有的输出器。

cpp 复制代码
//继承 public std::enable_shared_from_this<Logger> 可以在成员函数获得自己的shard_ptr
class Logger : public std::enable_shared_from_this<Logger>{
    public:
        typedef std::shared_ptr<Logger> ptr;
        typedef SpinkLock MutexType;

        Logger(const std::string name = "root");
        void log(LogEvent::ptr event);

        void setLevel(LogLevel::Level level) {m_level = level;}
        LogLevel::Level getLevel(){return m_level;}
        const std::string& getName() const {return m_name;}

        void addAppender(LogAppender::ptr appender);
        void delAppender(LogAppender::ptr appender);
        void clearAppender();

        std::string toYamlString();
    private:
        std::string m_name;                         //日志名称
        LogLevel::Level m_level;                    //日志级别,当事件的日志级别大于等于该日志器的级别时,才输出
        std::list<LogAppender::ptr> m_appenders;    //Appender集合
        MutexType m_mutex;
};

输出时,遍历所有的 appenders 进行输出。

cpp 复制代码
void Logger::log(LogEvent::ptr event){
    if(event->getLevel() >= m_level){
        // todo
        auto self = shared_from_this();
        for(auto& i: m_appenders){
            i->log(self, event);
        }
    }
}

LogEventWrap

使用LogEventWarp管理 event和logger,在析构的时候,调用logger的方法 流式输出 event

cpp 复制代码
class LogEventWrap{
    public:
        LogEventWrap(Logger::ptr logger, LogEvent::ptr event);
        ~LogEventWrap();
        std::stringstream& getSS() {return m_event->getSS();}
        LogEvent::ptr getEvent() {return m_event;}
    private:
        Logger::ptr m_logger; 
        LogEvent::ptr m_event;
};

// cpp
LogEventWrap::LogEventWrap(Logger::ptr logger, LogEvent::ptr event)
    :m_logger(logger), m_event(event){}

LogEventWrap::~LogEventWrap(){
    m_logger->log(m_event);
}

构造 LogEventWrap,获取到 LogEvent 的 m_ss,用户可以通过 << 写入 日志信息 同时支持 printf 格式

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

#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
#define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)
#define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)
#define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)
#define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)

// printf形式
#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
    if(logger->getLevel() <= level) \
        sylar::LogEventWrap(logger, sylar::LogEvent::ptr( \
            new sylar::LogEvent(__FILE__, __LINE__, 0 , \
                            sylar::GetThreadId(), sylar::Thread::GetName(), \
                            sylar::GetFiberId(), time(0), level))).getEvent()->format(fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_DEBUG(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, format,  __VA_ARGS__)
#define SYLAR_LOG_FMT_INFO(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_WARN(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_ERROR(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_FATAL(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, format, __VA_ARGS__) 


#define SYLAR_LOG_ROOT() sylar::LoggerMgr::GetInstance()->getRoot()
#define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger(name)

#define SYLAR_Log_YAML2String() sylar::LoggerMgr::GetInstance()->toYamlString()

LoggerManager

整个系统可以存在多个 Logger 日志器。

cpp 复制代码
class LoggerManager{
    public:
        typedef SpinkLock MutexType;
        LoggerManager();
        Logger::ptr getLogger(const std::string& name);
        Logger::ptr getRoot(){return m_root;}
        void init();
        std::string toYamlString(); // 将实例序列化
    private:
        MutexType m_mutex;
        std::map<std::string, Logger::ptr> m_loggers;
        Logger::ptr m_root;
};
cpp 复制代码
LoggerManager::LoggerManager(){
    m_root.reset(new Logger);               // 默认构造Logger,此时level是UNKNOW最低
    // 默认是输出std,跟随m_root的level,以及使用默认的格式器
    m_root->addAppender(LogAppender::ptr(new StdoutLogAppender(m_root->getLevel(), LogFormatter::ptr(new LogFormatter()))));  
    m_loggers[m_root->getName()] = m_root;  // 添加到m_loggers
    // 当配置项发生改变,能改变这个 m_root 的。(见日志系统⭐)
}

/**
 * 如果指定名称的日志器未找到,那么就新创建一个,新创建的 Logger 配置默认的 Appender和默认的 LogFormatter 
 */
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));
    // 添加一个默认的 std 输出器
    logger->addAppender(LogAppender::ptr(new StdoutLogAppender(m_root->getLevel(), LogFormatter::ptr(new LogFormatter()))));
    m_loggers[name] = logger;
    return logger;
}

备注

当前的日志系统是细粒度的锁,只针对 LogAppender 锁。(高效)

知识点

1. 使用 智能指针 高效管理实例

2. 使用宏定义简化switch

cpp 复制代码
const char* LogLevel::ToString(LogLevel::Level level){
    switch(level){
#define XX(name) \
    case LogLevel::name: \
        return #name; \	// #name 表示字符串
        break;

    XX(DEBUG);
    XX(INFO);
    XX(WARN);
    XX(ERROR);
    XX(FATAL);
#undef XX
    default:
        return "UNKNOW";
    }
    return "UNKNOW";
}

3. 宏定义 + lambda

cpp 复制代码
	/**
     * %m -- 消息体
     * %p -- level
     * %r -- 启动后的时间
     * %c -- 日志名称
     * %t -- 线程id
     * %F -- 协程id
     * %l -- 行号
     * %d -- 时间
     * %n -- 回车换行
     * %f -- 文件名
     * %T -- Tab
     * %s -- string
     */
    // 这里的map存储着,每个字符对于的仿函数。
    // 普通字符串 以及 DataTime的 FormatterItem 子类 需要初始化str。所以会有一个传入str的构造函数。
    static std::map<std::string, std::function<FormatterItem::ptr(const std::string& str)>> s_format_items = {
#define XX(str , C) \
        {#str , [](const std::string& fmt){return FormatterItem::ptr(new C(fmt));}}
        // #str 表示 字符串
        XX(m , MessageFormatterItem),
        XX(p , LevelFormatterItem),
        XX(r , ElapseFormatterItem),
        XX(c , LoggerNameFormatterItem),
        XX(t , ThreadIdFormatterItem),
        XX(F , FiberIdFormatterItem),
        XX(l , LineFormatterItem),
        XX(d , DataTimeFormatterItem),
        XX(n , NewLineFormatterItem),
        XX(f , FilenameFormatterItem),
        XX(T , TabFormatterItem),
#undef XX
    };

    // for循环中的auto&用于引用遍历容器中的元素。 
    // 最终得到每一个FormatterItem的子类的对象指针
    // m_items内的顺序其实就是m_pattern格式解析的顺序。
    for(auto& i : vec){
        if(std::get<2>(i) == 0){
            // 普通字符串输出 的 vec里 三元组 是 <str , "", 0> 
            m_items.push_back(FormatterItem::ptr(new StringFormatterItem(std::get<0>(i))));
        } else {
        	// 需要解析的 vec 三元组 是 <str, fmt, 1>
            auto it = s_format_items.find(std::get<0>(i));
            if(it == s_format_items.end()){
                m_items.push_back(FormatterItem::ptr(new StringFormatterItem("<< error_format %" + std::get<1>(i) + ">>")));
            } else {
                m_items.push_back(it->second(std::get<1>(i)));
            }
        }
    }

4. 宏定义简化调用

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

#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
#define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)
#define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)
#define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)
#define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)
// 提供另一种不同个 << 传入,而是类似 printf 的。
#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
    if(logger->getLevel() <= level) \
        sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(__FILE__, __LINE__, 0, \
                                                                            sylar::GetThreadId(), sylar::GetFiberId(), \
                                                                            time(0), level))).getEvent()->format(fmt, __VA_ARGS__)
#define SYLAR_LOG_FMT_DEBUG(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, format,  __VA_ARGS__)
#define SYLAR_LOG_FMT_INFO(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_WARN(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_ERROR(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, format, __VA_ARGS__)
#define SYLAR_LOG_FMT_FATAL(logger, format, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, format, __VA_ARGS__)                                                                      

5. 实现C风格的输出(va_list 与 vasprintf)

cpp 复制代码
#include <sstream>
#include <stdarg.h>

std::string format(const char* fmt, ...){
	std::stringstream ss;
	va_list args;			//定义了一个变量args,用于存储可变参数列表。
	va_start(args,fmt);		//初始化args,使其指向fmt之后的第一个参数。这是处理可变参数列表的必要步骤。
	char* buf = nullptr;
	/**
	vasprintf
	这是一个C语言函数,用于将可变参数列表args按照格式化字符串fmt格式化为一个字符串,
	并将其存储到动态分配的内存中,buf指向该内存。
	该函数返回格式化后的字符串长度(不包括结尾的\0),如果失败则返回-1。
	*/
	int len = vasprintf(&buf,fmt,args);
	if(len != -1){
		ss<<std::string(buf,len);
		free(buf);
	}
	va_end(args);
	return ss.str();
}

6. RAII (使用匿名对象 析构 进行流式输出)

匿名对象:执行完后立即析构 具名对象(栈内):超出作用域后析构 具名对象(堆内):手动释放后析构

cpp 复制代码
#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
    if(logger->getLevel() <= level) \ //创建匿名对象
        sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(__FILE__, __LINE__, 0, \
                                                                            sylar::GetThreadId(), sylar::GetFiberId(), \
                                                                            time(0), level))).getEvent()->format(fmt, __VA_ARGS__)

7. 模板实现单例

cpp 复制代码
#ifndef __SYLAR_SINGLETON_H_
#define __SYLAR_SINGLETON_H_

namespace sylar {

//X 为了创造多个实例对应的Tag
//N 同一个Tag创造多个实例索引
template<class T, class X = void, int N = 0>
class Singleton {
public:
    static T* GetInstance() {
        static T v;
        return &v;
    }
};

//X 为了创造多个实例对应的Tag
//N 同一个Tag创造多个实例索引
template<class T, class X = void, int N = 0>
class SingletonPtr {
public:
    static std::shared_ptr<T> GetInstance() {
        static std::shared_ptr<T> v(new T);
        return v;
    }
};
}
#endif

8. 时间戳获取和格式转换

cpp 复制代码
int main(int argc,char** argv){
    const std::string m_format = "%Y-%m-%d %H:%M:%S";
    struct tm tm;
    time_t t = time(0);
    localtime_r(&t, &tm);	//使用 localtime_r 函数将时间戳 t 转换为本地时间,并将结果存储到 tm 结构体中。
    char buf[64];
    // 使用 strftime 函数将 tm 结构体中的时间信息格式化为字符串,按照 m_format 中的格式输出到 buf 中。
    strftime(buf, sizeof(buf), m_format.c_str(), &tm);
    std::cout << buf << std::endl;
	return 0;
}

9. 线程号获取(PID)

cpp 复制代码
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>

int main(int argc,char** argv){
    std::cout << syscall(SYS_gettid) << std::endl;
    return 0;
}

潜在改进点

  • 添加LogAppender: Rolling File Appender,循环覆盖写文件 Rolling Memory Appender,循环覆盖写内存缓冲区 支持日志文件按大小分片或是按日期分片 支持网络日志服务器,比如syslog
相关推荐
成都第一深情IZZO4 小时前
工厂模式(使用Spring Bean)和 策略工厂模式 的区别
后端
该用户已不存在5 小时前
写了这么多年Java,这几个神仙技巧你用过吗?
java·后端
大雨淅淅5 小时前
【编程语言】Rust 入门
开发语言·后端·rust
桃花键神5 小时前
【送书福利-第四十四期】《 深入Rust标准库》
开发语言·后端·rust
像风一样自由20205 小时前
使用Rust构建高性能文件搜索工具
开发语言·后端·rust
xuejianxinokok6 小时前
io_uring 快吗? Postgres 17 与 18 的基准测试
数据库·后端·postgresql
DokiDoki之父6 小时前
SpringMVC—REST风格 & Restful入门案例 & 拦截器简介 & 拦截器入门案例 & 拦截器参数 & 拦截器链配置
后端·restful
JohnYan6 小时前
安全密钥(Security Key)和认证技术相关词汇表
后端·安全·设计模式
北海道浪子6 小时前
[免费送$1000]ClaudeCode、Codex等AI模型在开发中的使用
前端·人工智能·后端