C++高性能服务器框架 – SYLAR – 02日志模块

sylar
2019.08.02 16:46:29阅读 5点赞 0收藏 0更多
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

类图

类图

设计思想

仿照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; }