类图

类图

设计思想

仿照log4j的模式
将日志抽象成Logger(日志器),LogAppender(输出落地点),LogFormat(日志格式器)三大模块。
Logger, 对外使用的类,输入的日志级别大于等于Logger的日志,才会被真正写入。可以有多个Logger,不同的logger,记录不同类型的日志,比如将系统框架日志和业务逻辑日志分离。

LogAppender, 定义日志的输出落地点,目前实现了控制台日志(StdoutLogAppender),文件日志(FileLogAppender).两种类型。拥有自己的日志级别和日志格式,可以灵活定义不同的输出。主要用于区分日志级别,将error日志,单独输出到一个文件,以防被其他类型的日志淹没

LogFormat,日志格式,通过字符串自定义日志的格式,仿printf格式。可以灵活定义日志格式

核心数据结构

LogFormat

日志格式器,执行日志格式化,负责日志格式的初始化。
解析日志格式,将用户自定义的日志格式,解析为对应的FormatItem。
日志格式举例:%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n
格式解析:
%d{%Y-%m-%d %H:%M:%S} : %d 标识输出的是时间 {%Y-%m-%d %H:%M:%S}为时间格式,可选 DateTimeFormatItem
%T : Tab[\t]            TabFormatItem
%t : 线程id             ThreadIdFormatItem
%N : 线程名称           ThreadNameFormatItem
%F : 协程id             FiberIdFormatItem
%p : 日志级别           LevelFormatItem       
%c : 日志名称           NameFormatItem
%f : 文件名             FilenameFormatItem
%l : 行号               LineFormatItem
%m : 日志内容           MessageFormatItem
%n : 换行符[\r\n]       NewLineFormatItem

具体日志:
2019-06-17 00:28:45     9368    main    6       [INFO]  [system]        sylar/tcp_server.cc:64  server bind success: [Socket sock=9 is_connected=0 family=2 type=1 protocol=0 local_address=0.0.0.0:8020]
 class LogFormatter {
 public:
     typedef std::shared_ptr<LogFormatter> ptr;
     LogFormatter(const std::string& pattern);
     //将LogEvent格式化成字符串
     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() {}
         // 将对于的日志格式内容写入到os
         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;
     //通过日志格式解析出来的FormatItem,支持扩展
     std::vector<FormatItem::ptr> m_items;
     bool m_error = false;

 };

LogAppender

日志落地点抽象。目前只要实现了输出到控制台(StdoutLogAppender)和输出到文件(FileLogAppender),LogAppender可以拥有自己的LogFormat。
一个日志器,可以对应多个LogAppender。也就是说写一条日志,可以落到多个输出,并且每个输出的格式都可以不一样。
Appender有单独的日志级别,可以自定义不同级别的日志,输出到不同的Appender,常用于将错误日志统一输出到一个地方。
以后可以通过扩展LogAppender,实现向日志服务器写日志(利用socket)
class LogAppender {
 friend class 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格式的配置
     virtual std::string toYamlString() = 0;

     void setFormatter(LogFormatter::ptr val);

     LogFormatter::ptr getFormatter();

     LogLevel::Level getLevel() const { return m_level;}

     void setLevel(LogLevel::Level val) { m_level = val;}
 protected:
     LogLevel::Level m_level = LogLevel::DEBUG;
     bool m_hasFormatter = false;
     MutexType m_mutex;
     //日志格式器
     LogFormatter::ptr m_formatter;
 };

  //输出到控制台
  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;
 };

 //输出到日志文件
 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;
     std::string toYamlString() override;

     bool reopen();
 private:
     //文件名
     std::string m_filename;
     std::ofstream m_filestream;
     uint64_t m_lastTime = 0;
 };

Logger

日志器,包含一个日志格式器,一个root Logger,N个LogAppender
提供日志写入方法。根据日志器的配置格式和内容。将日志写到对应的地方
 class Logger : public std::enable_shared_from_this<Logger> {
 friend class LoggerManager;
 public:
     typedef std::shared_ptr<Logger> ptr;
     typedef Spinlock MutexType;

     Logger(const std::string& name = "root");
     // 写入日志,指定日志级别
     void log(LogLevel::Level level, LogEvent::ptr event);
     // 写debug日志
     void debug(LogEvent::ptr event);
     // 写info日志
     void info(LogEvent::ptr event);
     // 写warn日志
     void warn(LogEvent::ptr event);
     // 写error日志
     void error(LogEvent::ptr event);
     // 写fatal日志
     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);
     // 设置文本日志格式
     void setFormatter(const std::string& val);

     LogFormatter::ptr getFormatter();
     // 转成Yaml格式的配置文本
     std::string toYamlString();
 private:
     std::string m_name;
     //日志级别,低于该级别不会输出
     LogLevel::Level m_level;
     MutexType m_mutex;
     //appender集合
     std::list<LogAppender::ptr> m_appenders;
     //日志格式
     LogFormatter::ptr m_formatter;
     //主日志器,如果当前日志未定义,使用主日志器输出
     Logger::ptr m_root;
 };

LogManager

管理所有的日志器,并且可以通过解析Yaml配置,动态创建或修改日志器相关的内容(日志级别,日志格式,输出落地点等等)

 class LoggerManager {
 public:
     typedef Spinlock MutexType;
     LoggerManager();
     // 获取名称为name的日志器
     // 如果name不存在,则创建一个,并使用root配置
     Logger::ptr getLogger(const std::string& name);

     void init();

     Logger::ptr getRoot() const { return m_root;}
     // 转成yaml格式的配置
     std::string toYamlString();
 private:
     MutexType m_mutex;
     // 所有日志器
     std::map<std::string, Logger::ptr> m_loggers;
     // 主日志器(默认日志器)
     Logger::ptr m_root;
 };

 typedef sylar::Singleton<LoggerManager> LoggerMgr;

LogEvent

日志事件的封装,将要写的日志,填充到LogEvent中。填充完毕之后,写入到对应的logger中

 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;
     // 线程id
     uint32_t m_threadId = 0;
     // 协程id
     uint32_t m_fiberId = 0;
     // 日志事件
     uint64_t m_time = 0;
     // 线程名称
     std::string m_threadName;
     // 线程消息体流
     std::stringstream m_ss;
     // 目标日志器
     std::shared_ptr<Logger> m_logger;
     // 日志级别
     LogLevel::Level m_level;
 };
 // 日志事件包装类型(利用析构函数,触发日志写入)
 class LogEventWrap {
 public:

     LogEventWrap(LogEvent::ptr e);

     ~LogEventWrap();

     LogEvent::ptr getEvent() const { return m_event;}

     std::stringstream& getSS();
 private:
     LogEvent::ptr m_event;
 };

常用宏

为了写代码中方便,快捷的使用日志,提供的简便宏

 //使用logger写入日志级别为level的日志(流式日志)
 #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()
 //使用logger写入日志界别为debug的日志(流式日志)
 #define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
 //使用logger写入日志界别为info的日志(流式日志)
 #define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)
 //使用logger写入日志界别为warn的日志(流式日志)
 #define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)
 //使用logger写入日志界别为error的日志(流式日志)
 #define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)
 //使用logger写入日志界别为fatal的日志(流式日志)
 #define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)
 //使用logger写入日志级别为level的日志(格式化,printf)
 #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__)
 //使用logger写入日志界别为debug的日志(格式化,printf)
 #define SYLAR_LOG_FMT_DEBUG(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, fmt, __VA_ARGS__)
 //使用logger写入日志界别为info的日志(格式化,printf)
 #define SYLAR_LOG_FMT_INFO(logger, fmt, ...)  SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, fmt, __VA_ARGS__)
 //使用logger写入日志界别为warn的日志(格式化,printf)
 #define SYLAR_LOG_FMT_WARN(logger, fmt, ...)  SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, fmt, __VA_ARGS__)
 //使用logger写入日志界别为error的日志(格式化,printf)
 #define SYLAR_LOG_FMT_ERROR(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, fmt, __VA_ARGS__)
 //使用logger写入日志界别为fatal的日志(格式化,printf)
 #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)

使用示例代码

#include "sylar/log.h"

//定义一个日志器(这里使用的是root)
static sylar::Logger::ptr g_logger = SYLAR_LOG_ROOT();

int main(int argc, char** argv) {
    // 使用流式风格写日志
    SYLAR_LOG_INFO(g_logger) << "hello logger stream";
    // 使用格式化写日志
    SYLAR_LOG_FMT_INFO(g_logger, "%s", "hello logger format");
    return 0;
}
打赏 赞(338)
微信
支付宝
微信二维码图片

微信扫描二维码打赏

支付宝二维码图片

支付宝扫描二维码打赏

发表评论

邮箱地址不会被公开。 必填项已用*标注