github地址: https://github.com/sylar-yin

UML类图

Log.png

详解

首先整个日志模块主要分为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思想很有用,方便资源管理。