高性能服务器框架 (1)日志模块
github地址: https://github.com/sylar-yin
UML类图
详解
首先整个日志模块主要分为Logger、LogEvent、LogAppender、LogFormatter四个器件,是不是和log4j很像,没错,sylar 就是仿log4J设计的。如果了解log4J的代码的话可以跳过了。
首先整体就是Logger存储日志等级(Level)、格式(Formatter)以及输出的位置(Appender)这些信息,调用log()这个方法格式输出到Appender所实现的子类中。但是调用log()需要LogEvent作为传入参数。而LogEvent的作用是通过stringstream字符串流获得输入内容,既可以直接<<输入也可以调用format()方法进行格式化输入(类似于printf(“%d”,i)这样)。
接下来分部分来看。
LogFormatter
这个是格式器。主要就是用来对对每个格式进行format格式解析的。
构造函数中必须传入一个string 来初始化m_pattern,m_pattern就是输出的格式,之后会在init()方法中对m_pattern进行解析,根据里面的内容实例化出FormatterItem并放入序列容器中。便于之后调用Format()方法进行输出内容。
类中定义的FormatterItem 抽象类 ,就是将每个细分下去的格式进行抽象,其子类需要实现formt() 格式解析的方法。
例如
class LevelFormatItem : public LogFormatter::FormatItem {
public:
LevelFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << LogLevel::ToString(level);
}//对os输出流进行内容输出。
};
class MessageFormatItem : public LogFormatter::FormatItem {
public:
MessageFormatItem(const std::string& str = "") {}
void format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override {
os << event->getContent();
}
};
格式器的主要解析格式过程在于LogFormatter中的init()方法中,在这个方法里,首先通过对m_pattern 格式字符串进行一个解析,之后将解析得到的关键字符与已经实现出来的Item进行匹配,如果有的话就实例化出具体的对象,如果没有就实例化出一个存储错误信息的Item,最后放入到序列化容器中。
//%xxx %xxx{xxx} %%
void LogFormatter::init() {
//str, format, type
std::vector<std::tuple<std::string, std::string, int> > vec;
std::string nstr;
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);
//std::cout << "*" << str << std::endl;
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);
//std::cout << "# " << fmt << std::endl;
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));
}
static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)> > s_format_items = {
# define XX(str, C) \
{# str, [](const std::string& fmt) { return FormatItem::ptr(new C(fmt));}}
XX(m, MessageFormatItem), //m:消息
XX(p, LevelFormatItem), //p:日志级别
XX(r, ElapseFormatItem), //r:累计毫秒数
XX(c, NameFormatItem), //c:日志名称
XX(t, ThreadIdFormatItem), //t:线程id
XX(n, NewLineFormatItem), //n:换行
XX(d, DateTimeFormatItem), //d:时间
XX(f, FilenameFormatItem), //f:文件名
XX(l, LineFormatItem), //l:行号
XX(T, TabFormatItem), //T:Tab
XX(F, FiberIdFormatItem), //F:协程id
XX(N, ThreadNameFormatItem), //N:线程名称
# 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)));
}
}
//std::cout << "(" << std::get<0>(i) << ") - (" << std::get<1>(i) << ") - (" << std::get<2>(i) << ")" << std::endl;
}
//std::cout << m_items.size() << std::endl;
}
根据init() 所实现的方法我们可以明确格式字符串支持 %% %x %x{...}
这几种格式。
输出的动作已经完成了,对于这个输出动作format(std::ostream& os, Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) 的参数来说,还需要一个输出流。这个输出流谁来提供? 就是LogAppender.
LogAppender
这个类就是确定一个输出位置的容器。假如我们要实现一个远程日志的话就需要在这里继承这个抽象类,实现他的log()方法。
这个类主要的工作就是初始化输出流,并将输出流通过上面的format()传递到LogFormatter中。
LogEvent 与 Logger
LogEvent通过里面的stringstream 提供内容,Logger通过调用Log()进行写入日志内容。其中Log()实际上是在调用Appender的Log(),不过又封装了一层。
LogEventWrap
这个是对LogEvent 的再包装,根据RAII思想,在析构的时候进行日志写入,非常nice 。主要用在宏里面,方便快速写入相应等级的日志。
LogLevel 和 LogManage
这两个很简单,一个就是枚举等级,一个就是通过Logger 的名称来管理多个Logger。
总结
总体来说代码不多,把UML图画出来后能理解挺多内容的。不过里面的宏的时候看了很久,最后看到LogEventWrap的析构函数才恍然大悟,原来是在析构的时候调用log()进行写入。我还找了半天找不到哪里写入日志的。RAII思想很有用,方便资源管理。