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<LogEvent> 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<LogFormatter> 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<FormatItem> ptr;
/**
* @brief 析构函数
*/
virtual ~FormatItem() {}
/**
* @brief 格式化日志事件
*/
virtual void format(std::ostream &os, LogEvent::ptr event) = 0;
};
private:
/// 日志格式模板
std::string m_pattern;
/// 解析后的格式模板数组
std::vector<FormatItem::ptr> 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<LogAppender> 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<StdoutLogAppender> 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<Logger> 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<LogAppender::ptr> 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()不用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<std::string, Logger::ptr> 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 可以根据是否已经定义了一个变量来进行分支选择,其作用是:
- 防止头文件的重复包含和编译;
- 便于程序的调试和移植;
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中的内容向字节流中输出内容,调用结束。