[C++]sylar高性能服务器框架学习记录:日志模块

记录一下最近这学期学习的sylar服务器框架项目,输出整理一下项目的结构,用到的知识和自己的体会

项目仓库地址 https://github.com/sylar-yin/sylar/

整理博客过程中参考的大佬资料链接:

============================================

基础介绍

日志模块实现了自定义式化/流式输出日志,支持输出时间戳,行号,日志器名称,日志等级,线程ID,协程ID和日志消息,支持将日志输出到标准输出以及文件(可自行扩展socket等appender),支持根据不同的日志输出地(appender)以及不同的日志级别(level)将日志记录分流管理,同时也通过后续的线程模块实现了线程安全,也整合了配置模块,能通过yaml文件进行配置,还封装了一些宏提供方便的调用接口,有很好的扩展性和很高的可用性。

日志模块能大体分成以下几个类

  • LogLevel日志等级类

  • LogEvent日志事件类

  • LogEventWrap日志事件包装器

  • LogFormatter日志格式器类

  • LogAppender日志输出地类(抽象基类,后续派生)

  • Logger日志器类

  • LoggerManager日志管理器类(使用单例模式)

LogLevel日志等级类

在类内使用枚举定义了6种日志等级,并提供了枚举值和字符串之间相互转换的方法,其中使用了XX宏技巧简化了函数实现

// 日志级别类
class LogLevel {
public:
    // 日志级别枚举
    enum Level {
        UNKNOW = 0,     // 未知
        DEBUG = 1,      // 调试
        INFO = 2,       // 信息
        WARN = 3,       // 警告
        ERROR = 4,      // 错误
        FATAL = 5       // 致命
    };
    // 字符串方法
    static const char* ToString(LogLevel::Level level);
    static LogLevel::Level FromString(const std::string& str);
};
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";
} 

LogEvent日志事件类

该类的成员变量就是日志信息的各个项目,如行号,线程id,日志信息(存在stringstream中)等,同时还记录了所属日志器(智能指针形式,sylar项目中每个类都有定义智能指针托管,几乎不使用裸指针),提供了set和get方法以及字符串相关方法

// 日志事件类
class LogEvent{
public:
    typedef std::shared_ptr
<LogEvent> ptr;
    // 构造函数,传入日志器,以及其他日志信息
    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);

    // 获取日志信息的方法
    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; }
    uint32_t getFiberId() const { return m_fiberId; }
    uint64_t getTime() const { return m_time; }
    const std::string& getThreadName() const { return m_threadName; }
    std::string getContent() const { return m_ss.str(); }
    std::shared_ptr
<Logger> getLogger() const { return m_logger; }
    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;        // 线程id
    uint32_t m_fiberId = 0;         // 协程id
    uint64_t m_time = 0;            // 时间戳
    std::string m_threadName;       // 线程名
    std::stringstream m_ss;         // 字符串流(存消息体)

    std::shared_ptr
<Logger> m_logger;   // 日志器
    LogLevel::Level m_level;            // 日志级别
};

LogEventWrap日志事件包装器

该类用于最后日志宏的实现,利用RAII机制,在构造函数中传入日志事件指针,在析构函数中调用日志器的log方法打印日志

// 日志事件包装器类
class LogEventWrap {
public:
    // 构造函数(初始化好要包装的Event)
    LogEventWrap(LogEvent::ptr event);
    // 析构函数(析构的时候将Event写到Logger内)
    ~LogEventWrap();

    // 获取包装的Event的字符串流
    std::stringstream& getSS();
    // 获取包装的Event
    LogEvent::ptr getEvent() const { return m_event; }
private:
    LogEvent::ptr m_event;  // 包装的Event
};

LogEventWrap::LogEventWrap(LogEvent::ptr event)
    : m_event(event) {   
}

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

LogFormatter日志格式器类

这个类用于将日志格式字符串(pattern)解析,按照其格式将日志项编排,最后返回日志行字符串

在这个类的名称空间中声明了一个public类FormatItem,为抽象类,有一个虚方法format,后续在实现文件中继承该类,为每种日志项都实现各自的format方式,插入到输出流

// 日志格式器类(将日志内容格式化)
class LogFormatter{
public:
    typedef std::shared_ptr
<LogFormatter> ptr;
    // 构造函数,传入std::string类型的格式化模板,内部调用init方法,解析模板,填充m_items
    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();
    // 返回格式化时是否错误
    bool isError() const { return m_error; }
    // 获取格式化模板
    const std::string getPattern() const { return m_pattern; }
private:
    std::string m_pattern;                  // 格式化的模板
    std::vector<FormatItem::ptr> m_items;   // 存放日志格式解析后的日志项
    bool m_error = false;                   // 记录格式化过程是否有错误
};

// 以下是继承自LogFormatter::FormatItem的各种日志项类,实现了基类中的format方法,将日志项内容插入到输出流中
// 消息体format
class MessageFormatItem : public LogFormatter::FormatItem {
public:
    MessageFormatItem(const std::string& str = "") {}
    void format(std::ostream& os, std::shared_ptr
<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
        os << event->getContent();
    }
};

// 日志级别format
class LevelFormatItem : public LogFormatter::FormatItem {
public:
    LevelFormatItem(const std::string& str = "") {}
    void format(std::ostream& os, std::shared_ptr
<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
        os << LogLevel::ToString(level);
    }
};

// ...

// 解析的方法
// %xxx %xxx{xxx} %%
void LogFormatter::init() {
    // str, format, type
    std::vector<std::tuple<std::string, std::string, int>> vec;
    std::string nstr; // normal_string
    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()) {
            if(m_pattern[i + 1] == '%') {
                nstr.append(1, '%');
                continue;
            }
        }

        size_t n = i + 1;
        int fmt_status = 0;
        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);
                }
            }
        }

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

    if(!nstr.empty()) {
        vec.push_back(std::make_tuple(nstr, "", 0));
    }

    // %m -- 消息体 // %p -- level // %r -- 启动后时间 
    // %t -- 线程id // %n -- 回车换行 // %d -- 时间
    // %f -- 文件名 // %l -- 行号
    static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)>> s_format_items = {
#define XX(str, Class) \
        {#str, [](const std::string& fmt) { return std::make_shared
<Class>(fmt); }}

        XX(m, MessageFormatItem),
        XX(p, LevelFormatItem),
        XX(r, ElapseFormatItem),
        XX(c, NameFormatItem),
        XX(t, ThreadIdFormatItem),
        XX(n, NewLineFormatItem),
        XX(d, DateTimeFormatItem),
        XX(f, FilenameFormatItem),
        XX(l, LineFormatItem),
        XX(T, TabFormatItem),
        XX(F, FiberIdFormatItem),
        XX(N, ThreadNameFormatItem)
#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) + ">>")));
                m_error = true;
            } else {
                m_items.push_back(it->second(std::get
<1>(i)));
            }
        }
    }
}

LogFormatter::LogFormatter(const std::string& pattern)
    : m_pattern(pattern) {
    // 初始化formatter
    init();
}

std::string LogFormatter::format(std::shared_ptr
<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
    std::stringstream ss;
    for(auto& i : m_items) {
        // 遍历解析后的日志项,挨个调用format(使用基类指针,利用多态机制)
        i->format(ss, logger, level, event);    
    }
    // 返回最终的日志字符串
    return ss.str();
}

LogAppender日志输出地类

该类成员变量记录了日志级别,日志格式器,以及是否拥有pattern(没有的话使用默认模板),该类有两个子类,分别是StdoutLogAppender和FileLogAppender,分别输出到控制台标准输出和文件,两个子类各自实现了基类的纯虚方法log,实现了将日志格式器返回的日志字符串输出到相应位置,如果想要实现如网络日志等功能,只需再加一个SocketLogAppender类即可

// 日志输出地类(抽象类)
class LogAppender{
friend class Logger;    // 声明Logger为友类,便于Logger访问自己的成员
public:
    typedef std::shared_ptr
<LogAppender> ptr;
    typedef Spinlock MutexType;
    virtual ~LogAppender() {}

    // 输出到流
    virtual void log(std::shared_ptr
<Logger> logger, LogLevel::Level level, LogEvent::ptr event) = 0;
    // 返回序列化成YAML格式的appender配置信息的std::string
    virtual std::string toYamlString() = 0;

    // 设置日志格式器
    void setFormatter(LogFormatter::ptr val);
    // 获取日志格式器
    LogFormatter::ptr getFormatter();

    // 获取日志级别
    LogLevel::Level getLevel() const { return m_level; }
    // 设置日志级别
    void setLevel(LogLevel::Level level) { m_level = level; }
protected:
    LogLevel::Level m_level = LogLevel::DEBUG;  // 日志级别
    LogFormatter::ptr m_formatter;              // 日志格式器
    MutexType m_mutex;                          // 互斥锁
    bool m_hasFormatter = false;                // 是否有formatter
};

void StdoutLogAppender::log(std::shared_ptr
<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
    if(level >= m_level) {
        MutexType::Lock lock(m_mutex);
        std::cout << m_formatter->format(logger, level, event);
    }
}

void FileLogAppender::log(std::shared_ptr
<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
    if(level >= m_level) {
        uint64_t now = time(0);
        if(now != m_lastTime) {
            reopen();
            m_lastTime = now;
        }
        MutexType::Lock lock(m_mutex);
        if(!(m_filestream << m_formatter->format(logger, level, event))) {
            std::cout << "error" << std::endl;
        }

    }
}
// 输出到控制台的Appender类,继承自LogAppender
class StdoutLogAppender : public LogAppender {
public:
    typedef std::shared_ptr
<StdoutLogAppender> ptr;
    // 输出到std::cout
    virtual void log(std::shared_ptr
<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override;
    // 返回序列化成YAML格式的appender的配置信息
    std::string toYamlString() override;
};

// 输出到文件的Appender类,继承自LogAppender
class FileLogAppender : public LogAppender {
public:
    typedef std::shared_ptr
<FileLogAppender> ptr;
    // 构造函数,设置文件名并打开文件
    FileLogAppender(const std::string& filename);

    // 输出到文件
    virtual void log(std::shared_ptr
<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override;
    // 返回序列化成YAML格式的appender的配置信息
    std::string toYamlString() override;

    // 重新打开文件,成功打开返回true
    bool reopen();
private:
    std::string m_filename;         // 文件名
    std::ofstream m_filestream;     // 文件输出流
    uint64_t m_lastTime = 0;        // 记录上一次打开文件的时间
};

void StdoutLogAppender::log(std::shared_ptr
<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
    if(level >= m_level) {
        MutexType::Lock lock(m_mutex);
        std::cout << m_formatter->format(logger, level, event);
    }
}

void FileLogAppender::log(std::shared_ptr
<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
    if(level >= m_level) {
        uint64_t now = time(0);
        if(now != m_lastTime) {
            reopen();
            m_lastTime = now;
        }
        MutexType::Lock lock(m_mutex);
        if(!(m_filestream << m_formatter->format(logger, level, event))) {
            std::cout << "error" << std::endl;
        }

    }
}

Logger日志器类

成员变量记录了日志器名称,日志器级别(用于过滤),目的地appender的集合,日志格式器以及一个默认的root日志器,提供了默认的日志等级,日志器名称

// 日志器类
class Logger : public std::enable_shared_from_this
<Logger> {
friend class LoggerManager;
public:
    typedef std::shared_ptr
<Logger> ptr;
    typedef Spinlock MutexType;

    // 构造函数,日志器默认名称为root,级别为DEBUG,此外提供默认格式模板
    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 error(LogEvent::ptr event);
    void fatal(LogEvent::ptr event);

    // 增加appender
    void addAppender(LogAppender::ptr appender);
    // 删除appender
    void delAppender(LogAppender::ptr appender);
    // 清空appender
    void clearAppenders();
    // 获取日志器级别
    LogLevel::Level getLevel() const { return m_level; }
    // 设置日志器级别
    void setLevel(LogLevel::Level val) { m_level = val; }
    // 获取日志器名称
    const std::string& getName() const { return m_name; }

    // 设置日志格式器(使用格式器指针)
    void setFormatter(LogFormatter::ptr val);
    // 设置日志格式器(传入格式器名称自动new新的格式器)
    void setFormatter(const std::string& val);
    // 获取日志格式器
    LogFormatter::ptr getFormatter();

    // 返回序列化为YAML格式的std::string的Logger配置信息
    std::string toYamlString();
private:
    std::string m_name;                         // 日志名称
    LogLevel::Level m_level;                    // 日志级别
    MutexType m_mutex;                          // 互斥锁
    std::list<LogAppender::ptr> m_appenders;    // Appender的集合
    LogFormatter::ptr m_formatter;              // 日志格式器
    Logger::ptr         m_root;                 // 默认的root Logger
};

// 构造函数
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"));
                                     // 时间 线程id 协程id [日志级别] [日志器名称] 文件名:行号 日志信息
}

// log方法,往各个appender写日志
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) {
                // 调用appender类的log方法,将日志输出到各个appender
                i->log(self, level, event); 
            }
        } else if (m_root) {    // 若logger的appenders为空,使用默认root写log
            m_root->log(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::error(LogEvent::ptr event) {
    log(LogLevel::ERROR, event);
}
void Logger::fatal(LogEvent::ptr event) {
    log(LogLevel::FATAL, event);
}

LoggerManager日志管理器类

使用该类来对创建的一堆日志器进行全局管理,该类使用单例模式

// 日志管理器类
class LoggerManager {
public:
    typedef Spinlock MutexType;
    // 构造函数(初始化主日志器,默认为主日志器添加一个stdoutappender)
    LoggerManager();
    // 获取制定名称的日志器
    Logger::ptr getLogger(const std::string& name);

    // 初始化(虽然是一个空函数)
    void init();
    // 获取名字叫root的日志器
    Logger::ptr getRoot() const { return m_root; }

    // 返回序列化为YAML格式的所有Logger的配置信息的std::string
    std::string toYamlString();
private:
    MutexType m_mutex;                              // 互斥锁
    std::map<std::string, Logger::ptr> m_loggers;   // 存放日志器的map容器
    Logger::ptr m_root;                             // 主日志器
};

// 日志管理器的单例模式
typedef sylar::Singleton
<LoggerManager> LoggerMgr;

单例模式类的实现

#ifndef __SYLAR_SINGLETON_H__
#define __SYLAR_SINGLETON_H__

#include 
<memory>

namespace sylar {
// 裸指针形式
template<class T, class X = void, int N = 0>
class Singleton {
public:
    static T* GetInstance() {
        static T v;
        return &v;
    }
};
// 智能指针形式
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 // __SYLAR_SINGLETON_H__

提供了一套较为方便的日志宏,宏的设计十分巧妙,使用一个if语句判断日志事件级别是否高于日志器的级别,在if语句内部声明一个LogEventWrap实例,设置好相关信息,最后调用getSS()方法返回内部的stringstream,由于刚刚说的都是一条语句,故if不需要加花括号,这样当调用宏的时候可以直接在后面写<<符号插入日志消息

最后如果日志事件等级足够,if语句会被执行,执行完毕之后LogEventWrap会被析构,调用自己的Logger的log方法打印日志

// 用于流式输出的宏
#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()

#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)

// 用于格式化输出的宏
#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
    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()))).getEvent()->format(fmt, __VA_ARGS__)

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

// 用于获取日志器的宏
#define SYLAR_LOG_ROOT() sylar::LoggerMgr::GetInstance()->getRoot()
#define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger(name)

测试文件

在控制台和log.txt打印测试日志

#include "sylar/log.h"
#include "sylar/util.h"
#include 
<iostream>

int main(int argc, char** argv) {
    sylar::Logger::ptr logger(new sylar::Logger);
    logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutLogAppender));

    sylar::FileLogAppender::ptr file_appender(new sylar::FileLogAppender("./log.txt"));
    sylar::LogFormatter::ptr fmt(new sylar::LogFormatter("%d%T%m%n"));
    file_appender->setFormatter(fmt);
    file_appender->setLevel(sylar::LogLevel::ERROR);
    logger->addAppender(file_appender);

    SYLAR_LOG_INFO(logger) << "test info" << " test again";
    SYLAR_LOG_ERROR(logger) << "test error";
    SYLAR_LOG_FATAL(logger) << "test fatal";

    sylar::Logger::ptr logger2(new sylar::Logger("Ayanami"));
    logger2->addAppender(sylar::LogAppender::ptr(new sylar::StdoutLogAppender));

    SYLAR_LOG_ERROR(logger2) << "my log test";

    SYLAR_LOG_FMT_DEBUG(logger, "i want to play %s and %s", "minecraft", "sleep");

    auto l = sylar::LoggerMgr::GetInstance()->getLogger("xx");
    SYLAR_LOG_INFO(l) << "singleton_test";

    return 0;
}

控制台运行结果如下

image-20260205234416783

log.txt也被成功写入,可以看到我们设置的格式也生效了(测试一共运行了三次)

image-20260205234558215

总结

sylar实现了一个可用性以及可扩展性很高的日志框架,为整个项目的日志提供了支持

评论

  1. Sankkooos
    Android Firefox 134.0
    19 小时前
    2026-2-07 0:42:07

    (•́⌄•́๑)૭✧

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇