日志模块主要有三个类:
LogFormatter:根据给定的格式字符串形成一个个FormatItem,不同类别的FormatItem有其特定的格式化输出方式
LogAppender:输出目的地,内含一个LogFormatter,负责将格式化后的字符串输出到指定的目的地。目前该类有两个子类,分别将日志输出到控制台和文件中。
Logger:日志器。提供输出日志方法的上层类。含有多个输出地点LogAppender,以及一个用于赋值的LogFormatter。
一条指定等级的日志想要被输出,需要经过Logger和Appender两层筛选,这两个类中各含一个输出等级,只有当前日志等级>=这两个类中的等级,日志才会被输出到目的地。
logger中含有多个输出地 Appender。每个Appender 中包含了一个formatter。formatter的format()方法遍历每个formatItem项目,再调用formatItem的format() 返回每项的格式化字符串结果,然后根据所属的Appender目的地,输出到指定输出流中
/*日志对象*/
class LogEvent {
public:
LogEvent(const char *fileName, int32_t linenum, uint32_t elapse, uint32_t
threadID, uint32_t fiberID, uint64_t time);
//定义指向LogEvent的智能指针 为类型 ptr
typedef std::shared_ptr<LogEvent> ptr;
const char* getFile(){ return m_file; }
int32_t getLineNum(){ return m_line; }
uint32_t getElapse(){ return m_elapse; }
uint32_t getThreadId(){ return m_threadId; }
uint32_t getFiberId(){ return m_fiberId; }
uint64_t getTime(){ return m_time; }
const std::string getContent(){ return sstream.str(); }
std::stringstream& getSS() { return sstream; }
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; //时间戳
std::stringstream sstream;//内容
};
LogEvent是日志本身。现有字段如上所示。
日志等级目前有5个。toString方法负责将等级对象转换成对应的字符串。
class LogLevel {
public:
enum Level{
UNKNOW = 0,
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4,
FATAL = 5
};
static const char *toString(Level level);
};
日志器是最终我们使用日志模块的入口。
它主要有这样几个字段:最低日志等级m_level,默认格式化器m_formatter,输出地集合m_appenders
主要方法是log(),负责将日志指定为某等级尝试输出到各个目的地。
/*日志器 定义日志类别*/
class Logger : public std::enable_shared_from_this<Logger> {
public:
typedef std::shared_ptr<Logger> ptr;
Logger(const std::string &name = "root");
void addAppender(LogAppender::ptr appender);
void delAppender(LogAppender::ptr appender);
LogLevel::Level getLevel() const { return m_level; }
void setLevel(LogLevel::Level level){m_level = level;}
const std::string& getName(){ return m_name; }
void log(LogLevel::Level level, const 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);
private:
std::string m_name;
LogLevel::Level m_level; //只有满足该日志级别的日志才会被它输出
std::list<LogAppender::ptr> m_appenders; //输出目的地的集合
LogFormatter::ptr m_formatter; //默认的格式化器,用于赋值给 m_appenders 中的formatter
};
LogAppender负责实际将日志输出到指定地点。
它的主要字段是:最低输出等级m_level 、格式化器m_formatter
其主要方法也是log(),
/*日志输出地*/
class LogAppender {
public:
// 将来会有控制台和文件两种Appender ,析构的的方式不同
virtual ~LogAppender(){};
typedef std::shared_ptr<LogAppender> ptr;
//定义为纯虚函数,其子类必须实现该方法
virtual void log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) =0;
void setFormatter(LogFormatter::ptr val){ m_formatter = val; }
LogFormatter::ptr getFormatter() const { return m_formatter; }
protected: //此级别目的是为了使子类可见 外部不可见
LogLevel::Level m_level = LogLevel::DEBUG;
LogFormatter::ptr m_formatter;
};
它有两个子类,分别输出到控制台和文件
//标准输出作为输出地点
class StdoutLogAppender:public LogAppender {
public:
typedef std::shared_ptr<StdoutLogAppender> ptr;
virtual void log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) override;
private:
};
//文件作为输出地点
class FileLogAppender:public LogAppender {
public:
FileLogAppender(const std::string &filename);
typedef std::shared_ptr<FileLogAppender> ptr;
virtual void log(std::shared_ptr<Logger> logger ,LogLevel::Level level, LogEvent::ptr event) override;
bool reopen(); // 如果已经打开了,就先关闭再打开
private:
std::string m_filename;
std::ofstream m_filestream;
};
LogFormatter为日志格式化器。
主要字段:格式化字符串 m_pattern ,日志每一项的格式化方式m_items
主要方法:init()解析指导日志格式的格式字符串。
//日志格式器
class LogFormatter {
public:
typedef std::shared_ptr<LogFormatter> ptr;
// virtual ~LogFormatter(){};
LogFormatter(const std::string &pattern);
std::string format(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event); //返回格式化后的字符串
void init();//解析格式字符串,用来指导日志的输出方式
public:
class FormatItem {
public:
typedef std::shared_ptr<FormatItem> ptr;
FormatItem(const std::string &fmt = "") {};
virtual ~FormatItem() {};
virtual void format(std::ostream &ostream,std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event)=0;
};
private:
std::vector<FormatItem::ptr> m_items;//存放日志的各个部分
std::string m_pattern; // 指明格式化方式的字符串
int char_cat(char ch);
enum status{
ST_ERROR = -2,
ST_BREAK =-1,
ST_TYPE_STR=0,
ST_FORMAT_STR=1,
ST_END=2
};
// SP { } ch
int nfa[3][5]={{ST_BREAK,ST_FORMAT_STR,ST_ERROR,ST_BREAK,ST_TYPE_STR},
{ST_ERROR,ST_ERROR,ST_END,ST_FORMAT_STR,ST_FORMAT_STR},
{ST_ERROR,ST_END,ST_END,ST_END,ST_END}};
};
日志模块中主要的类已经介绍完了。下面看一看将一条日志输出需要经过哪些流程:
Logger::log
→
\rightarrow
→ 遍历所有输出地 LogAppender::log
→
\rightarrow
→ 对每个输出目的地中的格式化器 LogFormatter::format
→
\rightarrow
→ 对格式化器中的日志项 列表FormatItem::format
void Logger::log(LogLevel::Level level, const LogEvent::ptr event) {
if (level >= m_level) {
auto self = shared_from_this();
for (auto &it : m_appenders) {
it->log(self,level, event);
}
}
}
void StdoutLogAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
if (level >= m_level) {
std::cout << m_formatter->format(logger, level, event);
}
}
std::string LogFormatter::format(std::shared_ptr<Logger> logger, LogLevel::Level level, sylar::LogEvent::ptr event) {
std::stringstream ss;
for (auto &item : m_items) {
item->format(ss, logger, level, event);
}
return ss.str();
}
这里只列出两种FormatItem
:
class DateTimeFormatItem : public LogFormatter::FormatItem {
public:
DateTimeFormatItem(const std::string &formatstr = "%Y-%m-%d %H:%M:%S") : m_format(formatstr) {
if (m_format.empty()) {
m_format = "%Y-%m-%d %H:%M:%S";
}
}
void format(std::ostream &ostream, std::shared_ptr<Logger> logger, LogLevel::Level level,
LogEvent::ptr event) override {
struct tm tm;
time_t time = event->getTime();
// localtime_r(&time,&tm) //linux
localtime_s(&tm, &time); //windows
char buf[64];
strftime(buf, sizeof(buf), m_format.c_str(), &tm);
ostream << buf;
}
private:
std::string m_format;
};
/*====================================================================================*/
class StringFormatItem : public LogFormatter::FormatItem {
public:
StringFormatItem(const std::string& str):FormatItem(str),m_string(str) {}
void format(std::ostream &ostream, std::shared_ptr<Logger> logger, LogLevel::Level level,
LogEvent::ptr event) override {
ostream << m_string;
}
private:
std::string m_string;
};
现在的问题就是,LogFormatter::format
中遍历的m_items
是怎么得到的?
这就要求助于 formatter 中的init()
函数了,它根据格式字符串,先是为日志的每个部分生成了一个3元组,再将这些三元组按顺序填充了m_items
格式字符串 "%d{%Y-%m-%d}%T[%p]%T<%f : %l>%T%m%n"
各个部分的3元组:
(d) - (%Y-%m-%d) - (1)
(T) - () - (1)
([) - () - (0)
(p) - () - (1)
(]) - () - (0)
(T) - () - (1)
(<) - () - (0)
(f) - () - (1)
( : ) - () - (0)
(l) - () - (1)
(>) - () - (0)
(T) - () - (1)
(m) - () - (1)
(n) - () - (1)
2021-06-16 [DEBUG] <C:\Users\huyaorui\CLionProjects\SylarServer\sylar\testLog.cpp : 14> hello sylar-log
2021-06-16 [ERROR] <C:\Users\huyaorui\CLionProjects\SylarServer\sylar\testLog.cpp : 18> something wrong!!
init()
方法负责解析格式字符串,将每个部分拆成一个个三元组,最后根据类型字符串来选择不同类型的FormatItem
加入到有序列表m_items
中。
首先我们来观察格式字符串中的一项 %d{%Y-%m-%d}
其形式为%X{...}
%
后的字符表示类别,%d
表示这项是日期时间。不妨将其称作类型字符串。
{ }
中的内容是指导用何种样式来输出日期时间。不妨将其称为格式字符串。
init解析出格式每项,将其放入到三元组( 类型字符串,格式字符串,是否按字面值输出 )
有序列表中,而后初始化一个MAP,将其负责将三元组按类型字符映射为其对应类别的XxxFormatItem
对象,并放入有序列表m_items
下表为解析日志格式化字符串的状态机:
void LogFormatter::init() {
std::vector<std::tuple<std::string, std::string, int>> vector;//三元组有序列表(类型字符串,格式字符串,是否按字面值输出)
std::string nstr;//主要负责接受格式字符串中按字面值输出的普通字符串(就是除了%、{、} )
int state ;//状态
for (size_t i = 0; i < m_pattern.size(); ++i) {
if (m_pattern[i] != '%') { //只要没遇见 % ,就将所读到的字符加到nstr
nstr.append(1, m_pattern[i]);
continue;
}
if (i + 1 < m_pattern.size() && m_pattern[i + 1] == '%') {//如果是出现了连续两个%% ,视作%字符本身,也加入nstr
nstr.append(1, '%');
continue;
}
/*
* 只有遇到%后才能进入下面这部分!! 表示开始解析形如 %X{...} 的部分
*/
state = ST_TYPE_STR; //初始状态 = 类型字符串 %XX
size_t n = i + 1;//从%后的第一个字符开始
std::string typeStr;
std::string formatStr;
int formatStart = -1; //如果有格式字符串,其开始的下标
// 在字符串没读完的情况下,这三种状态都可能使循环结束
while (state != ST_BREAK && state != ST_END && state != ST_ERROR && n < m_pattern.size()) {
int cat = char_cat(m_pattern[n]);//判断当前读入字符的类别
if (cat >= 0) {
state = nfa[state][cat];
} else {//如果不属于空格、{、} 、字母、%、- 中的任何一个,则将状态置为 ST_BREAK;
// state = ST_ERROR;
// break;
state = ST_BREAK;
}
switch (state) {
case ST_FORMAT_STR:
if (formatStart == -1 ) {//当首次读到{ 从而状态跳变为ST_FORMAT_STR时执行:
typeStr = m_pattern.substr(i + 1, n - i -1);
formatStart = n; //字符 { 的位置
}
break;
case ST_END:
formatStr = m_pattern.substr(formatStart + 1, n - formatStart - 1);
break;
}
++n;
}
if (n >= m_pattern.size()) { //若是因为读到字符串的末尾而跳出while循环,则有可能没能解析完最后的部分
if (!nstr.empty()) {
vector.push_back(std::make_tuple(nstr, "", 0));
nstr.clear();
}
if (formatStart != -1) { n = formatStart; } //若这最后一部分含格式字符串{xxx},则应将n置为 { 的位置,这样才能保证下面能正确地将typeStr取出
typeStr = m_pattern.substr(i + 1, n - i - 1);
vector.push_back(std::make_tuple(typeStr, formatStr, 1));
i = m_pattern.size();
break;
}
if (state == ST_BREAK) {
if (!nstr.empty()) {
vector.push_back(std::make_tuple(nstr, "", 0));
nstr.clear();
}
typeStr = m_pattern.substr(i + 1, n - i - 2);
vector.push_back(std::make_tuple(typeStr, formatStr, 1));
i = n-2;//为抵消while中的 ++n和外层for 中的++i
} else if (state == ST_ERROR) {
std::cout << "Format string error:" << m_pattern << " - " << m_pattern.substr(i) << std::endl;
vector.push_back(std::make_tuple("<<Mode declaration error>>", formatStr, 0));
} else if (state == ST_END) { //完整地解析了%X{xxx}这个部分
if (!nstr.empty()) {
vector.push_back(std::make_tuple(nstr, "", 0));
nstr.clear();
}
vector.push_back(std::make_tuple(typeStr, formatStr, 1));
i = n-1; //while中的 ++n 导致多读了一位
//此时i所对应的字符是},因此不用再-1去抵消掉for 中的++i
}
}
static std::map<std::string, std::function<FormatItem::ptr(const std::string &str)> > s_format_items = {
#define YY(str, CLASS)\
{ #str, [](const std::string &fmt) {return FormatItem::ptr(new CLASS(fmt));} }
YY(m, MessageFormatItem),
YY(p, LevelFormatItem),
YY(r, ElapseFormatItem),
YY(c, NameFormatItem),
YY(t, ThreadFormatItem),
YY(n, NewLineFormatItem),
YY(d, DateTimeFormatItem),
YY(f, FileFormatItem),
YY(l, LineNumFormatItem),
YY(T, TabFormatItem),
#undef YY
};
for (auto &i : vector) {
if (std::get<2>(i) == 0) { // 三元组的最后一个元素==0 这表示它按照字面值输出即可,不需要格式化输出
m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(i))));//直接把三元组中的第一位交给StringFormatItem 做初始化
} else { //三元组中的第一位是类型字符,需要格式化后输出
auto item = s_format_items.find(std::get<0>(i)); //在MAP中用格式字符串寻找对应的FormatItem对象
if (item==s_format_items.end()) {//没有找到
m_items.push_back(FormatItem::ptr(new StringFormatItem("<<Unsupported format: %"+std::get<0>(i)+">>")));
} else { //找到了
m_items.push_back(item->second(std::get<1>(i))); //用三元组中的第2个元素:格式字符串{xxx} 来创建对应类别的FormatItem
}
}
//测试:打印出三元组
// std::cout <<"("<< std::get<0>(i) << ") - (" << std::get<1>(i) << ") - (" << std::get<2>(i)<<")" << std::endl;
}
}