C++高性能服务器框架 – SYLAR – 02日志模块
类图
设计思想
仿照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;
}