日志系统分为日志级别,日志事件,日志器,日志输出器,日志格式器,日志流几个部分。
图片说明


1.日志级别:

DEBUG INFO WARN ERROR FATAL
其中由低到高,UNKOWN充当0。ToString() 将日志级别转为字符串。
注1:对于在类中定义的enum类型,可以用类名::DEBUG直接获取。
注2:对于静态函数不可以声明为const函数。

class LogLevel {
public:
    enum Level {
    UNKOWN=0,
    DEBUG=1,
    INFO=2,
    WARN=3,
    ERROR=4,
    FATAL=5
    };
    static const char * ToString(LogLevel::Level level);
};

2. 日志事件

日志事件中有要输出的主要信息,包括文件、日志输出当前行号、从程序启动到现在的毫秒数、线程号、协程号、时间戳(用于时间转化)。
stringstream主要用于输出使用者想要输出的信息。
注1:getSS()未定义为const函数,是因为const函数返回引用必须为const类型,而在此处需要后续输入所以不能定义为const函数。
注2:在LogEvent中加入logger以及level是为了在定义流日志格式时更加方便的调用日志器。
注3:format函数,是为了让用户在输出自己的信息时,使用自己的格式进行输出。具体实现使用vasprintf函数,vasprintf(buf,fmt,va_list),va_list实现变参输入。

//日志事件
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 thread_id,uint32_t elapse,uint32_t fiber_id,uint64_t time);
    const char * getFile () const {return m_file;}
    uint32_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;}
    std::string  getContent() const {return m_ss.str();}
    std::stringstream & getSS() { return m_ss;}

    std::shared_ptr<Logger> getLogger() const { return m_logger;}
    LogLevel::Level getLevel() const { return m_level;}
    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;              // 从程序启动到现在的毫秒数
    uint32_t m_threadId=0;          //线程号
    uint32_t m_fiberId=0;           //协程id
    uint64_t m_time;                //时间戳
    std::stringstream m_ss ;         
    std::shared_ptr<Logger> m_logger;
    LogLevel::Level m_level;
};

3日志流

LogEventWrap类主要为了实现日志流的实现,使用户体验更加良好,不用费事去定义LogEvent去进行日志输出。
日志流使用宏的方式,直接定义LogEventWrap类,并为日志事件付与日志器与日志级别等初始参数。并且直接返回日志事件中的stringstream用于用户输入自己要输出的内容。最优秀的部分来了,大神的思路就是在LogEventWrap的析构函数中直接使用日志事件的日志器进行日志输出。因为这个宏定义只返回了日志事件中的输出流,所以在完成输出流的调用之后,会直接调用LogEventWrap的析构函数进行输出。

/**
 * @brief 使用流式方式将日志级别level的日志写入到logger
 */
#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)))).getSS()

/**
 * @brief 使用流式方式将日志级别debug的日志写入到logger
 */
#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)

/**
 * @brief 使用流式方式将日志级别info的日志写入到logger
 */
#define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)

/**
 * @brief 使用流式方式将日志级别warn的日志写入到logger
 */
#define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)

/**
 * @brief 使用流式方式将日志级别error的日志写入到logger
 */
#define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)

/**
 * @brief 使用流式方式将日志级别fatal的日志写入到logger
 */
#define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)

class LogEventWrap {
public:
    /**
     * @brief 构造函数
     * @param[in] e 日志事件
     */
    LogEventWrap(LogEvent::ptr e);
    /**
     * @brief 析构函数
     */
    ~LogEventWrap();
    /**
     * @brief 获取日志事件
     */
    LogEvent::ptr getEvent() const { return m_event;}
    /**
     * @brief 获取日志内容流
     */
    std::stringstream& getSS();
private:
    /**
     * @brief 日志事件
     */
    LogEvent::ptr m_event;
};

LogEventWrap::LogEventWrap(LogEvent::ptr e)
    :m_event(e) {
}

LogEventWrap::~LogEventWrap() {
    m_event->getLogger()->log(m_event->getLevel(), m_event);
}

std::stringstream& LogEventWrap::getSS() {
    return m_event->getSS();
}

4日志器

日志器的作用是对满足日志输出级别的日志事件进行输出,核心函数是log(),日志器本身带有级别,如果log传入的level大于日志器本身的级别则进行输出,至于输出的目标则由LogAppender来定义,包括控制台以及日志文件。
在Logger类中m_appenders是一个LogAppende基类的list,是因为可以输出到多个目标。
m_formatter是LogFormatter类型,也是基类用于定义日志输出的格式,在Logger的构造函数中就进行了初始化,

m_formatter.reset(new LogFormatter("%d{%Y:%m:%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"));

之所以选择在日志器中就进行日志格式的初始化,是因为用户在为日志器添加日志输出地时并不会为日志输出地定义日志输出格式,所以在日志器添加日志输出地时如果日志输出地未携带日志输出格式,默认为日志输出地添加日志输出格式。

void Logger:: addAppender(LogAppender::ptr appender){
    if(!appender->getFormatter()){

        appender->setFormatter(m_formatter);
    }
    m_appenders.push_back(appender);
}
void Logger::delAppender(LogAppender::ptr appender){
    for(auto it=m_appenders.begin();it != m_appenders.end();it++){
        if(*it==appender){
            m_appenders.erase(it);
            break;
        }
    }
}

为了在最底层输出日志器的名称,需要在log()函数中将日志器的shared_ptr指针传下去。这就需要Logger类继承std::enable_shared_from_this<logger>类,并在log()函数中调用shared_from_this()得到自己的智能指针。</logger>

void Logger::log(LogLevel::Level level,LogEvent::ptr  event){
   if(level >= m_level){
        auto self = shared_from_this();
        for(auto & i: m_appenders){
            i->log(self,level,event);
        }
   }
}
//日志器
class Logger : public std::enable_shared_from_this<Logger> {
public:
    typedef std::shared_ptr<Logger> ptr;
    Logger(const std::string & name ="root");
    void log(LogLevel::Level level,LogEvent::ptr  event);
    void debug(LogEvent::ptr event);
    void info(LogEvent::ptr event);
    void error(LogEvent::ptr event);
    void warn(LogEvent::ptr event);
    void fatal(LogEvent::ptr event); // 方便用户不使用日志级别输出log,间接调用log()函数
    void addAppender(LogAppender::ptr appender);
    void delAppender(LogAppender::ptr appender);
    LogLevel::Level getLevel() const { return m_level; }
    void setLevel(LogLevel::Level val){ m_level = val; }
    const std::string & getName() const {return m_name;}


private:
    std::string m_name;       //日志名称
    LogLevel::Level m_level;  //满足日志级别
    std::list<LogAppender::ptr> m_appenders; //Appender 集合  
    LogFormatter::ptr m_formatter;
};

5日志输出地

日志输出地主要以某种日志输出格式向文件或者控制台进行输出,核心函数为log()函数。
注1:C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。https://blog.csdn.net/Sunburst_Lf/article/details/91394642

//日志输出器
class LogAppender {
public:
    typedef std::shared_ptr<LogAppender> ptr;
    virtual ~LogAppender() {}
    virtual void log(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr  event) = 0;
    void setFormatter(LogFormatter::ptr val){ m_formatter = val;
       //  std::cout<<m_formatter->getPattern()<<"  $$$$$$$"<<std::endl;
     }
    LogFormatter::ptr getFormatter() const { return m_formatter; }
    LogLevel::Level getLevel() const { return m_level;}
    void setLevel(LogLevel::Level val) { m_level = val;}
protected:
    LogLevel::Level m_level=LogLevel::DEBUG;;
    LogFormatter::ptr m_formatter;
};

LogAppender有两个子类FileLogAppender,StdoutLogAppender分别定义向文件与控制台输出。其核心函数都是log(),调用自己的LogFormatter参数进行输出。其中FileLogAppender额外多了m_filename与m_filestream分别是文件名与文件流

//输出到控制台
class StdoutLogAppender :public LogAppender{
public:
    typedef std::shared_ptr<StdoutLogAppender> ptr;
    void log(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr  event) override;
private:
};
//输出到文件
class FileLogAppender :public LogAppender{
public:
    typedef std::shared_ptr<FileLogAppender> ptr;
    FileLogAppender(const std::string & filename);
    void log(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr  event) override;
    //重新打开文件,成功返回true
    bool reopen();
private:
    std::string m_filename;
    std::ofstream m_filestream;

};

FileLogAppender::FileLogAppender(const std::string & filename):m_filename(filename){
    reopen();
}
void FileLogAppender::log(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr  event) {
    if(m_level <= level){
        m_filestream << m_formatter->format(logger,level,event);
    }

}
bool FileLogAppender::reopen(){
    if(m_filestream){
        m_filestream.close();
    }
    m_filestream.open(m_filename);
    return !!m_filestream;
}

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);
    }

}

6日志输出格式

日志输出格式一层可以说是日志系统的最底层了,这一层主要包含了对m_pattern的解析,解析函数为init()函数,这个函数实现了将m_pattern中的内容转化为FormatItem的各项子类,对日志格式中的各项进行输出,日志格式主要包含以下几种。

        xx(m,MessageFormatItem),       //消息体
        xx(p,LevelFormatItem),         //level
        xx(r,ElapseFormatItem),        //启动后时间
        xx(c,NameFormatItem),          //日志名称
        xx(t,ThreadIdFormatItem),      //线程id
        xx(n,NewLineFormatItem),       //换行
        xx(d,DateTimeFormatItem),      //时间
        xx(f,FilenameFormatItem),      //文件名
        xx(l,LineFormatItem),           //行号
    xx(T,TabFormatItem),           //\t
    xx(F,FiberIdFormatItem),       //协程号

接下来是init()函数对m_pattern进行解析,例如:%d{%Y:%m:%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T%m%n

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] != &#39;%&#39;) {
            nstr.append(1, m_pattern[i]);
            continue;
        }

        if((i + 1) < m_pattern.size()) {
            if(m_pattern[i + 1] == &#39;%&#39;) {
                nstr.append(1, &#39;%&#39;);
                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] != &#39;{&#39;
                    && m_pattern[n] != &#39;}&#39;)) {
                str = m_pattern.substr(i + 1, n - i - 1);
                break;
            }
            if(fmt_status == 0) {
                if(m_pattern[n] == &#39;{&#39;) {
                    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] == &#39;}&#39;) {
                    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));}}
        // {#str,[](const std::string & fmt) {return LogFormatter::FormatItem::ptr(new C(fmt)); }}
        xx(m,MessageFormatItem),       //消息体
        xx(p,LevelFormatItem),         //level
        xx(r,ElapseFormatItem),        //启动后时间
        xx(c,NameFormatItem),          //日志名称
        xx(t,ThreadIdFormatItem),      //线程id
        xx(n,NewLineFormatItem),       //换行
        xx(d,DateTimeFormatItem),      //时间
        xx(f,FilenameFormatItem),      //文件名
        xx(l,LineFormatItem),           //行号
        xx(T,TabFormatItem),           //\t
        xx(F,FiberIdFormatItem),           //协程号
#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;
}
//日志格式器
class LogFormatter{
public:
    typedef std::shared_ptr<LogFormatter> ptr;
    std::string format(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event);
    LogFormatter(const std::string & pattern);
    std::string getPattern() const{ return m_pattern;}
    class FormatItem{
        public:
            typedef std::shared_ptr<FormatItem> ptr;

            virtual ~FormatItem() {}
            virtual void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) =0;
    };
    void init();
private:
    std::string m_pattern;
    std::vector<FormatItem::ptr> m_items; 
};

以下是格式解析单元类:

//输出格式项目
class MessageFormatItem : public LogFormatter::FormatItem {
public:
    MessageFormatItem(const std::string & str = ""){}
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<event->getContent();
    }

};

class LevelFormatItem : public LogFormatter::FormatItem{
 public:
    LevelFormatItem(const std::string & str = ""){}
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<LogLevel::ToString(level);
    }


};
class ElapseFormatItem : public LogFormatter::FormatItem {
 public:
    ElapseFormatItem(const std::string & str = ""){}
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<event->getElapse();
    }
};

class NameFormatItem : public LogFormatter::FormatItem {
 public:
    NameFormatItem(const std::string & str = ""){}
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<logger->getName();
    }   
};

class ThreadIdFormatItem : public LogFormatter::FormatItem {
 public:
    ThreadIdFormatItem(const std::string & str = ""){}
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<event->getThreadId();
    }   
};

class FiberIdFormatItem : public LogFormatter::FormatItem{
 public:
    FiberIdFormatItem(const std::string & str = ""){}
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<event->getFiberId();
    }   
};

class FilenameFormatItem : public LogFormatter::FormatItem{
 public:
    FilenameFormatItem(const std::string & str = ""){}
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<event->getFile();
    }   
};

class LineFormatItem : public LogFormatter::FormatItem{
 public:
    LineFormatItem(const std::string & str = ""){}
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<event->getLine();
    }   
};

class StringFormatItem : public LogFormatter::FormatItem{
 public:

    StringFormatItem(const std::string & str):m_string(str){
    }
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<m_string;
    }   
private:
    std::string m_string; 
};

class NewLineFormatItem : public LogFormatter::FormatItem{
 public:
    NewLineFormatItem(const std::string & str = ""){}
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<std::endl;
    }   
};


class TabFormatItem : public LogFormatter::FormatItem{
 public:
    TabFormatItem(const std::string & str = ""){}
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
        os<<&#39;\t&#39;;
    }   
};

其中包含对时间戳转换时间格式说明。

class DateTimeFormatItem : public LogFormatter::FormatItem{
 public:
   // DateTimeFormatItem(const std::string & format="%Y:%m:%d %H:%M:%s"):m_format(format){}
    DateTimeFormatItem(const std::string& format = "%Y-%m-%d %H:%M:%S")
        :m_format(format) {
        if(m_format.empty()) {
            m_format = "%Y-%m-%d %H:%M:%S";
        }
    }
    void format(std::ostream & os,std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
       // os<<event->getTime();
       struct tm tm;
       time_t time = event->getTime();
       localtime_r(&time, &tm);
       char buf[64];
       strftime(buf, sizeof(buf), m_format.c_str(), &tm);
       os << buf;
    }   
private:
    std::string m_format;
};

7.日志管理器

日志管理器主要用来提供其他系统调用Logger的接口,方便去管理日志,相当于一个日志池,可以去池子里面取日志器.

class LoggerManager {
public:
    /**
     * @brief 构造函数
     */
    LoggerManager();

    /**
     * @brief 获取日志器
     * @param[in] name 日志器名称
     */
    Logger::ptr getLogger(const std::string& name);

    /**
     * @brief 初始化
     */
    void init();
    /**
     * @brief 返回主日志器
     */
    Logger::ptr getRoot() const { return m_root;}
private:
    /// 日志器容器
    std::map<std::string, Logger::ptr> m_loggers;
    /// 主日志器
    Logger::ptr m_root;
};

/// 日志器管理类单例模式
typedef sylar::Singleton<LoggerManager> LoggerMgr;


/**
 * @brief 单例模式封装类
 * @details T 类型
 *          X 为了创造多个实例对应的Tag
 *          N 同一个Tag创造多个实例索引
 */
template<class T, class X = void, int N = 0>
class Singleton {
public:
    /**
     * @brief 返回单例裸指针
     */
    static T* GetInstance() {
        static T v;
        return &v;
        //return &GetInstanceX<T, X, N>();
    }
};

用宏定义去方便的获取管理器中的日志器,直接获取根日志器,或者根据名字获取。

 * @brief 获取主日志器
 */
#define SYLAR_LOG_ROOT() sylar::LoggerMgr::GetInstance()->getRoot()

/**
 * @brief 获取name的日志器
 */
#define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger(name)