【源码讲解】sylar服务器框架----日志模块

1.日志模块类图

【源码讲解】sylar服务器框架----日志模块_第1张图片

2.本模块所包含的类

  • LogLevel

  • LogEvent

  • LogFormatter

  • LogAppender
  • StdoutLogAppender

  • FileLogAppender

  • Logger

  • LogEventWrap

  • LoggerManager

3.各类讲解

       LogLevel

                其中使用枚举类型区分日志级别,参考log4cpp,级别越低就越有可能输出越紧急,级别越高就越有不可能输出越不紧急,日志等级如下表:

FATAL  = 0

致命情况,系统不可用

ALERT  = 100

高优先级情况,例如数据库系统崩溃

CRIT   = 200

严重错误,例如硬盘错误

ERROR  = 300

错误

WARN   = 400

警告

NOTICE = 500

正常但值得注意

INFO   = 600

一般信息

DEBUG  = 700

调试信息

NOTSET = 800

未设置

                        有两个静态成员函数,起到的作用分别是日志级别转字符串和字符串转日志级别,这里采用了宏定义的方式来简化代码。

例如:

        XX(FATAL)宏展开后是case LogLevel::FATAL: return "FATAL";

        XX(FATAL, fatal)宏展开后是if(str == "fatal") { return LogLevel::FATAL; }

代码如下:

声明:
/**
 * @brief 日志级别
 */
class LogLevel {
public:
    /**
     * @brief 日志级别枚举,参考log4cpp
     */
    enum Level { 
        /// 致命情况,系统不可用
        FATAL  = 0,
        /// 高优先级情况,例如数据库系统崩溃
        ALERT  = 100,
        /// 严重错误,例如硬盘错误
        CRIT   = 200,
        /// 错误
        ERROR  = 300,
        /// 警告
        WARN   = 400,
        /// 正常但值得注意
        NOTICE = 500,
        /// 一般信息
        INFO   = 600,
        /// 调试信息
        DEBUG  = 700,
        /// 未设置
        NOTSET = 800,
    };

    /**
     * @brief 日志级别转字符串
     * @param[in] level 日志级别 
     * @return 字符串形式的日志级别
     */
    static const char *ToString(LogLevel::Level level);

    /**
     * @brief 字符串转日志级别
     * @param[in] str 字符串 
     * @return 日志级别
     * @note 不区分大小写
     */
    static LogLevel::Level FromString(const std::string &str);
};

定义:


const char *LogLevel::ToString(LogLevel::Level level) {
    switch (level) {
#define XX(name) case LogLevel::name: return #name;
    XX(FATAL);
    XX(ALERT);
    XX(CRIT);
    XX(ERROR);
    XX(WARN);
    XX(NOTICE);
    XX(INFO);
    XX(DEBUG);
#undef XX
    default:
        return "NOTSET";
    }
    return "NOTSET";
}

LogLevel::Level LogLevel::FromString(const std::string &str) {
#define XX(level, v) if(str == #v) { return LogLevel::level; }
    XX(FATAL, fatal);
    XX(ALERT, alert);
    XX(CRIT, crit);
    XX(ERROR, error);
    XX(WARN, warn);
    XX(NOTICE, notice);
    XX(INFO, info);
    XX(DEBUG, debug);

    XX(FATAL, FATAL);
    XX(ALERT, ALERT);
    XX(CRIT, CRIT);
    XX(ERROR, ERROR);
    XX(WARN, WARN);
    XX(NOTICE, NOTICE);
    XX(INFO, INFO);
    XX(DEBUG, DEBUG);
#undef XX

    return LogLevel::NOTSET;
}

LogEvent

日志事件类,具体来说是存每一行日志的全部内容(只有内容没有格式)。通过构造函数传入日志内容,里面写了成员变量用于存储日志行号,线程id,日志内容等日志内容,并且用成员函数返回成员变量的内容。写了一个名为printf的成员函数用于实现用C的printf风格写入日志,里面通过va_list实现了类似printf的可变形参。

/**
 * @brief 日志事件
 */
class LogEvent {
public:
    typedef std::shared_ptr ptr;

    /**
     * @brief 构造函数
     * @param[in] logger_name 日志器名称
     * @param[in] level 日志级别
     * @param[in] file 文件名
     * @param[in] line 行号
     * @param[in] elapse 从日志器创建开始到当前的累计运行毫秒
     * @param[in] thead_id 线程id
     * @param[in] fiber_id 协程id
     * @param[in] time UTC时间
     * @param[in] thread_name 线程名称
     */
    LogEvent(const std::string &logger_name, LogLevel::Level level, const char *file, int32_t line
        , int64_t elapse, uint32_t thread_id, uint64_t fiber_id, time_t time, const std::string &thread_name);

    /**
     * @brief 获取日志级别
     */
    LogLevel::Level getLevel() const { return m_level; }

    /**
     * @brief 获取日志内容
     */
    std::string getContent() const { return m_ss.str(); }

    /**
     * @brief 获取文件名
     */
    std::string getFile() const { return m_file; }

    /**
     * @brief 获取行号
     */
    int32_t getLine() const { return m_line; }

    /**
     * @brief 获取累计运行毫秒数
     */
    int64_t getElapse() const { return m_elapse; }

    /**
     * @brief 获取线程id
     */
    uint32_t getThreadId() const { return m_threadId; }

    /**
     * @brief 获取协程id
     */
    uint64_t getFiberId() const { return m_fiberId; }

    /**
     * @brief 返回时间戳
     */
    time_t getTime() const { return m_time; }

    /**
     * @brief 获取线程名称
     */
    const std::string &getThreadName() const { return m_threadName; }

    /**
     * @brief 获取内容字节流,用于流式写入日志
     */
    std::stringstream &getSS() { return m_ss; }

    /**
     * @brief 获取日志器名称
     */
    const std::string &getLoggerName() const { return m_loggerName; }

    /**
     * @brief C prinf风格写入日志
     */
    void printf(const char *fmt, ...);

    /**
     * @brief C vprintf风格写入日志
     */
    void vprintf(const char *fmt, va_list ap);

private:
    /// 日志级别
    LogLevel::Level m_level;
    /// 日志内容,使用stringstream存储,便于流式写入日志
    std::stringstream m_ss;
    /// 文件名
    const char *m_file = nullptr;
    /// 行号
    int32_t m_line = 0;
    /// 从日志器创建开始到当前的耗时
    int64_t m_elapse = 0;
    /// 线程id
    uint32_t m_threadId = 0;
    /// 协程id
    uint64_t m_fiberId = 0;
    /// UTC时间戳
    time_t m_time;
    /// 线程名称
    std::string m_threadName;
    /// 日志器名称
    std::string m_loggerName;
};

LogFormatter

日志格式化类,通过构造函数传入日志格式。

默认格式:

%%d{%%Y-%%m-%%d %%H:%%M:%%S}%%T%%t%%T%%N%%T%%F%%T[%%p]%%T[%%c]%%T%%f:%%l%%T%%m%%n

默认格式描述:年-月-日 时:分:秒 [累计运行毫秒数] \\t 线程id \\t 线程名称 \\t 协程id \\t [日志级别] \\t [日志器名称] \\t 文件名:行号 \\t 日志消息 换行符

%%m

消息

%%p

日志级别

%%c

日志器名称

%%d

日期时间,后面可跟一对括号指定时间格式,比如%%d{%%Y-%%m-%%d %%H:%%M:%%S},这里的格式字符与C语言strftime一致

%%r

该日志器创建后的累计运行毫秒数

%%f

文件名

%%l

行号

%%t

线程id

%%F

协程id

%%N

线程名称

%%%

百分号

%%T

制表符

%%n

换行

        这个类中有个FormatItem成员类,他是所有日志内容格式化项的虚基类,用于派生出不同的格式化项,用来实现多态,这样子在格式模板数组中仅需存基类指针即可(通过智能指针进行管理)。

        在LogFormatter类外,通过继承FormatItem成员类派生出多个日志内容格式化项,每个模板参数对应着一个日志内容格式化项,比如消息的日志内容格式化项是MessageFormatItem,日志级别的日志内容格式化项是LevelFormatItem。每个日志内容格式化项都有一个format函数,用于格式化日志事件,函数的功能是向输入流中输出字符串形式的内容。

         构造函数会先传入日志格式(pattern),若没有参数则采用默认格式,然后调用init成员函数,解析格式模板,提取模板项。在init函数中,从pattern提取常规字符和模式字符。从头到尾进行遍历,根据状态标志决定当前是常规字符,还是正在解析模板转义字符,如果出现符号对应不上的情况,就停止解析,并且设置错误标志(m_error = true),解析完成后,若有错误则停止解析,若无错误则继续执行函数。在函数中有一个静态的成员类,这个成员类是哈希表,键是字符串形式的模板参数(比如消息是"m",日志级别是"p"),值是一个匿名函数,用于创建日志内容格式化项的对象并且返回指向这个对象的指针。这个哈希表是一个静态的成员类,所以只会被初始化一次,避免多次初始化哈希表拖慢运算速度。接下来会遍历字符,并且创建指向日志内容格式化项的对象的指针,放到解析后的格式模板数组(m_items)中,init函数执行结束。

        在LogFormatter类中,还有format函数用于对日志事件进行格式化(根据类中解析后的格式模板数组,将参数中的LogEvent对象转换成字符串形式),这个函数有两个版本,可以返回格式化日志文本或者日志流。具体实现就是根据解析后的格式模板数组,依次调用LogEvent中成员函数返回变量。

/**
 * @brief 日志格式化
 */
class LogFormatter {
public:
    typedef std::shared_ptr ptr;

    /**
     * @brief 构造函数
     * @param[in] pattern 格式模板,参考sylar与log4cpp
     */
    LogFormatter(const std::string &pattern = "%d{%Y-%m-%d %H:%M:%S} [%rms]%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n");

    /**
     * @brief 初始化,解析格式模板,提取模板项
     */
    void init();

    /**
     * @brief 模板解析是否出错
     */
    bool isError() const { return m_error; }

    /**
     * @brief 对日志事件进行格式化,返回格式化日志文本
     * @param[in] event 日志事件
     * @return 格式化日志字符串
     */
    std::string format(LogEvent::ptr event);

    /**
     * @brief 对日志事件进行格式化,返回格式化日志流
     * @param[in] event 日志事件
     * @param[in] os 日志输出流
     * @return 格式化日志流
     */
    std::ostream &format(std::ostream &os, LogEvent::ptr event);

    /**
     * @brief 获取pattern
     */
    std::string getPattern() const { return m_pattern; }

public:
    /**
     * @brief 日志内容格式化项,虚基类,用于派生出不同的格式化项
     */
    class FormatItem {
    public:
        typedef std::shared_ptr ptr;
        
        /**
         * @brief 析构函数
         */
        virtual ~FormatItem() {}

        /**
         * @brief 格式化日志事件
         */
        virtual void format(std::ostream &os, LogEvent::ptr event) = 0;
    };

private:
    /// 日志格式模板
    std::string m_pattern;
    /// 解析后的格式模板数组
    std::vector m_items;
    /// 是否出错
    bool m_error = false;
};

LogAppender

        这个类是日志输出地的虚基类,目前实现了StdoutLogAppender(输出到控制台)和FileLogAppender(输出到文件中)的派生类,以后可以也可以写其他的日志输出地,目前暂时只实现这两个。

        构造函数的参数传入LogFormatter(日志格式器),规定日志输出的时候若无LogFormatter,默认采用什么样的格式(此即约定优于配置)。类中还有成员函数用于设置日志格式器和获取日志格式器。类中还有用于向指定输出地点写入的纯虚函数log,将日志输出目标的配置转换成yamlstring的toYamlString函数。

/**
 * @brief 日志输出地,虚基类,用于派生出不同的LogAppender
 * @details 参考log4cpp,Appender自带一个默认的LogFormatter,以控件默认输出格式
 */
class LogAppender {
public:
    typedef std::shared_ptr ptr;
    typedef Spinlock MutexType;

    /**
     * @brief 构造函数
     * @param[in] default_formatter 默认日志格式器
     */
    LogAppender(LogFormatter::ptr default_formatter);
    
    /**
     * @brief 析构函数
     */
    virtual ~LogAppender() {}

    /**
     * @brief 设置日志格式器
     */
    void setFormatter(LogFormatter::ptr val);

    /**
     * @brief 获取日志格式器
     */
    LogFormatter::ptr getFormatter();

    /**
     * @brief 写入日志
     */
    virtual void log(LogEvent::ptr event) = 0;

    /**
     * @brief 将日志输出目标的配置转成YAML String
     */
    virtual std::string toYamlString() = 0;

protected:
    /// Mutex
    MutexType m_mutex;
    /// 日志格式器
    LogFormatter::ptr m_formatter;
    /// 默认日志格式器
    LogFormatter::ptr m_defaultFormatter;
};

StdoutLogAppender

        这个类是LogAppender的派生类,具体作用是输出字符到控制台上。

        重写了log函数,log函数内调用日志格式器的format函数,将日志内容输出到std::cout中。

        重写了toYamlString函数,在yaml对象中增加了type和pattern项,并以字符串形式返回。

/**
 * @brief 输出到控制台的Appender
 */
class StdoutLogAppender : public LogAppender {
public:
    typedef std::shared_ptr ptr;

    /**
     * @brief 构造函数
     */
    StdoutLogAppender();

    /**
     * @brief 写入日志
     */
    void log(LogEvent::ptr event) override;

    /**
     * @brief 将日志输出目标的配置转成YAML String
     */
    std::string toYamlString() override;
};

FileLogAppender

        这个类是LogAppender的派生类,具体作用是输出字符到文件里。

        构造函数传入字符串形式的日志文件路径作为参数,然后创建LogFormatter对象,调用repen函数打开指定文件。

        重写了log函数,log函数内调用日志格式器的format函数,将日志内容输出到文件流中,如果一个日志距离上次打开日志超过3秒的话,就再打开一次日志。

        重写了toYamlString函数,在yaml对象中增加了type和pattern和file项,并以字符串形式返回。

Logger

        这个是日志器类,用于向全部LogAppender中按照日志级别输出。比如说我想输出一个不是很重要的日志,调用Logger的log成员函数,会调用FileLogAppender中的log函数输出,但是却不会调用StdoutLogAppender中的log函数输出。所用LogAppender用一个lis进行存储。

        构造函数传入日志器名字,然后设置默认的日志等级。

        Logger类中写了addAppender用于添加日志输出地,delAppender用于删除日志输出地,clearAppenders用于删除全部日志输出地。

        Logger类中的log函数用于向全部LogAppender中输出,输出的时候会根据当前已经设置好的日志等级判断是否向这个LogAppender中输出。

        Logger类中还写了设置和获取日志级别,获取日志名称和创建时间的成员函数。

        Logger类中的toYamlString函数,在yaml对象中增加了name和level和全部appenders中的配置项,并以字符串形式返回。

        

/**
 * @brief 日志器类
 * @note 日志器类不带root logger
 */
class Logger{
public:
    typedef std::shared_ptr ptr;
    typedef Spinlock MutexType;

    /**
     * @brief 构造函数
     * @param[in] name 日志器名称 
     */
    Logger(const std::string &name="default");

    /**
     * @brief 获取日志器名称
     */
    const std::string &getName() const { return m_name; }

    /**
     * @brief 获取创建时间
     */
    const uint64_t &getCreateTime() const { return m_createTime; }

    /**
     * @brief 设置日志级别
     */
    void setLevel(LogLevel::Level level) { m_level = level; }

    /**
     * @brief 获取日志级别
     */
    LogLevel::Level getLevel() const { return m_level; }

    /**
     * @brief 添加LogAppender
     */
    void addAppender(LogAppender::ptr appender);

    /**
     * @brief 删除LogAppender
     */
    void delAppender(LogAppender::ptr appender);

    /**
     * @brief 清空LogAppender
     */
    void clearAppenders();

    /**
     * @brief 写日志
     */
    void log(LogEvent::ptr event);

    /**
     * @brief 将日志器的配置转成YAML String
     */
    std::string toYamlString();

private:
    /// Mutex
    MutexType m_mutex;
    /// 日志器名称
    std::string m_name;
    /// 日志器等级
    LogLevel::Level m_level;
    /// LogAppender集合
    std::list m_appenders;
    /// 创建时间(毫秒)
    uint64_t m_createTime;
};

LogEventWrap

        这个是日志事件包装器,方便宏定义,内部包含日志事件和日志器。

        构造函数传入日志器指针和日志事件指针。

        然后往日志事件中的输出流写入数据(输出流就是std::cin或者是std::ofstream这样得东西)。

       在LogEventWrap类析构的时候,才调用Logger的log函数进行输出。

        使用这个类的原因是为了方便宏定义,这样就不用在宏定义里写一堆东西了,构造函数只是传入两个指针,不涉及到其他对象的创建和销毁,不会损失太多的性能。

        

/**
 * @brief 日志事件包装器,方便宏定义,内部包含日志事件和日志器
 */
class LogEventWrap{
public:
    /**
     * @brief 构造函数
     * @param[in] logger 日志器 
     * @param[in] event 日志事件
     */
    LogEventWrap(Logger::ptr logger, LogEvent::ptr event);

    /**
     * @brief 析构函数
     * @details 日志事件在析构时由日志器进行输出
     */
    ~LogEventWrap();

    /**
     * @brief 获取日志事件
     */
    LogEvent::ptr getLogEvent() const { return m_event; }

private:
    /// 日志器
    Logger::ptr m_logger;
    /// 日志事件
    LogEvent::ptr m_event;
};

LoggerManager

        这个是日志器管理类,类中用map来存日志器的集合(这里不用vector或list的原因是数组或链表查找指定日志器的时间复杂度是O(n),用map查找的时间复杂度是O(\log n)不用unordered_map的原因是unordered_map的键不允许用自定义的数据类型)。另外还会存一个单独的root日志器。

        LoggerManager的构造函数里,会先初始化一个root日志器,接着会把这个root日志器放到map中方便查找,这个日志器内会先添加一个默认的FileLogAppender,接下来会调用init函数,从配置文件中加载日志配置。

        getLogger函数的功能是从类中的map中指定名称的日志器,如果没有的话就新创建一个,新创建的日志器是不带Appender的(没有添加默认的FileLogAppender)。

        toYamlString函数的功能是返回字符串形式的yaml文件内容,内容是每个日志器的配置。

/**
 * @brief 日志器管理类
 */
class LoggerManager{
public:
    typedef Spinlock MutexType;

    /**
     * @brief 构造函数
     */
    LoggerManager();

    /**
     * @brief 初始化,主要是结合配置模块实现日志模块初始化
     */
    void init();

    /**
     * @brief 获取指定名称的日志器
     */
    Logger::ptr getLogger(const std::string &name);

    /**
     * @brief 获取root日志器,等效于getLogger("root")
     */
    Logger::ptr getRoot() { return m_root; }

    /**
     * @brief 将所有的日志器配置转成YAML String
     */
    std::string toYamlString();

private:
    /// Mutex
    MutexType m_mutex;
    /// 日志器集合
    std::map m_loggers;
    /// root日志器
    Logger::ptr m_root;
};

4.宏定义部分

        为什么用流式方式:方便

        宏定义部分采用流式方式打印日志,源代码如下:

#define SYLAR_LOG_LEVEL(logger , level) \
    if(level <= logger->getLevel()) \
        sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(logger->getName(), \
            level, __FILE__, __LINE__, sylar::GetElapsedMS() - logger->getCreateTime(), \
            sylar::GetThreadId(), sylar::GetFiberId(), time(0), sylar::GetThreadName()))).getLogEvent()->getSS()

#define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)

#define SYLAR_LOG_ALERT(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ALERT)

#define SYLAR_LOG_CRIT(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::CRIT)

#define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)

#define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)

#define SYLAR_LOG_NOTICE(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::NOTICE)

#define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)

#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)

以SYLAR_LOG_FATAL(logger)为例子,宏展开后是

if(sylar::LogLevel::FATAL <= logger->getLevel()) {
    sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(logger->getName(),level, __FILE__, __LINE__, sylar::GetElapsedMS() - logger->getCreateTime(),sylar::GetThreadId(), sylar::GetFiberId(), time(0),sylar::GetThreadName()))).getLogEvent()->getSS()
}

         首先判断日志级别,若日志级别小于等于日志器所规定的日志级别的话,则允许输出,先构造出一个LogEvent,构造函数的参数通过宏定义以及一些工具函数获取,接着将logger(日志器)和LogEvent(日志事件)作为构造函数的参数创建一个LogEventWrap对象,接着调用getLogEvent方法返回一个LogEvent,接着调用LogEvent中的getSS方法,返回内容字节流(std::cin或者std::ofstream),然后往内容字节流中写入数据,getSS函数会返回字节流对象,所以就可以像cin那样,在后面接着一大长串的 << 。在LogEventWrap析构的时候自动调用Logger的log函数进行输出。

        使用例子:

SYLAR_LOG_FATAL(g_logger) << "fatal " << "msg";

         宏展开后:

if(sylar::LogLevel::FATAL <= logger->getLevel()) {
    sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(logger->getName(),level, __FILE__, __LINE__, sylar::GetElapsedMS() - logger->getCreateTime(),sylar::GetThreadId(), sylar::GetFiberId(), time(0),sylar::GetThreadName()))).getLogEvent()->getSS() << "fatal msg";
}

        本模块也支持使用C printf方式打印日志,源代码如下:

#define SYLAR_LOG_FMT_LEVEL(logger, level, fmt, ...) \
    if(level <= logger->getLevel()) \
        sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(logger->getName(), \
            level, __FILE__, __LINE__, sylar::GetElapsedMS() - logger->getCreateTime(), \
            sylar::GetThreadId(), sylar::GetFiberId(), time(0), sylar::GetThreadName()))).getLogEvent()->printf(fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_FATAL(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_ALERT(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ALERT, fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_CRIT(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::CRIT, fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_ERROR(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_WARN(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_NOTICE(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::NOTICE, fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_INFO(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, fmt, __VA_ARGS__)

#define SYLAR_LOG_FMT_DEBUG(logger, fmt, ...) SYLAR_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, fmt, __VA_ARGS__)

        以 SYLAR_LOG_FMT_FATAL(logger, fmt, ...)为例子,宏展开后是

if(sylar::LogLevel::FATAL <= logger->getLevel()) {
    sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(logger->getName(), level, __FILE__, __LINE__, sylar::GetElapsedMS() - logger->getCreateTime(), sylar::GetThreadId(), sylar::GetFiberId(), time(0), sylar::GetThreadName()))).getLogEvent()->printf(fmt, __VA_ARGS__)
}

         首先判断日志级别,若日志级别小于等于日志器所规定的日志级别的话,则允许输出,先构造出一个LogEvent,构造函数的参数通过宏定义以及一些工具函数获取,接着将logger(日志器)和LogEvent(日志事件)作为构造函数的参数创建一个LogEventWrap对象,接着调用getLogEvent方法返回一个LogEvent,接着调用LogEvent中的printf方法,向日期内容中写入数据,然后在LogEventWrap析构的时候自动调用Logger的log函数进行输出。

        使用例子:

SYLAR_LOG_FMT_FATAL(g_logger, "fatal %s:%d", __FILE__, __LINE__);

        宏展开后: 

if(sylar::LogLevel::FATAL <= logger->getLevel()) {
    sylar::LogEventWrap(logger, sylar::LogEvent::ptr(new sylar::LogEvent(logger->getName(), level, __FILE__, __LINE__, sylar::GetElapsedMS() - logger->getCreateTime(), sylar::GetThreadId(), sylar::GetFiberId(), time(0), sylar::GetThreadName()))).getLogEvent()->printf("fatal %s:%d", __FILE__, __LINE__)
}

两种输出方式比较:

SYLAR_LOG_FATAL(g_logger) << "fatal msg";
SYLAR_LOG_FMT_FATAL(g_logger, "fatal %s:%d", __FILE__, __LINE__);

 一个是流式方式,一个是C的printf输出方式,用哪个都行。

在控制台输出后的样子:

5.本模块的某些细节

         1.本模块中的头文件部分,例如:

#ifndef __SYLAR_LOG_H__
#define __SYLAR_LOG_H__

//代码

#endif

   

ifndef 可以根据是否已经定义了一个变量来进行分支选择,其作用是:

  1. 防止头文件的重复包含和编译;
  2. 便于程序的调试和移植;

        2.头文件中""和<>的区别

                <>只从默认头文件路径中寻找

                ""先从当前文件夹中寻找,然后再从默认头文件路径中寻找

2.本模块中LogAppender,Logger,LoggerManager中大量使用自旋锁,没用读写锁的原因是因为读写锁加锁解锁的时候资源损耗比较多,而且这里的代码也比较短,运算很快的。

3.使用日志模块的时候,首先利用单利模式封装类(以后再讲这个)返回一个LoggerManager(所有线程只有这一个LoggerManager),接着使用LoggerManager获取Logger,然后再通过这个Logger打印日志。

6.从创建LoggerManager到日志打印到控制台上的的全过程

        首先调用语句sylar::LoggerMgr::GetInstance()->getRoot(),首先通过单例模式获取唯一的LoggerManager(存在就直接获取,不存在就创建一个再获取),调用LoggerManager构造函数的时候会创建一个Logger,接着会创建出一个StdoutLogAppender,并把它加入到Logger中的LogAppender集合中,调用LogAppender构造函数的时候会创建一个LogFormatter(日志格式器)(先采用默认格式),然后调用LogFormatter的init函数(这个函数里面都干了什么建议可以到上面的各类讲解里面看),至此就拿到了Logger,并且已经创建完毕。

        到使用宏定义输出日志内容的时候,以SYLAR_LOG_FATAL(g_logger) << "fatal msg";为例子。

        这个宏定义在上面已经详细讲解,接下来讲向字节流输入完日志内容后,在LogEventWrap对象销毁调用析构函数时候发生的事。在LogEventWrap调用析构函数的时候,调用Logger中的log函数,在这个函数里面会先判断LogEvent的日志等级是否小于等于logger允许输出的日志等级(日志等级越小,日志越急迫,越应该输出),然后调用LogAppender中的log函数(还记得LogAppender是个虚基类么,其实这里只是用LogAppender的指针,它指向的是派生类),假如只有StdoutLogAppender的话,在log函数里面调用LogFormatter(日志格式器)的format函数(如果没设置LogFormatter就用默认的LogFormatter,否则就用用户自己设置的),在format函数里面调用FormatItem(日志内容格式化项)的format函数,然后根据日志内容格式化项对应的LogEvent中的内容向字节流中输出内容,调用结束。

你可能感兴趣的:(c++,开发语言)