日志模块概述
日志模块类似于Log4j风格。同样的,日志模块拥有以下几个主要类:
-
class LogLevel:定义日志级别。并提供将日志级别与文本之间的互相转化
-
class Logger:日志器。定义日志级别,设置输出地,设置日志格式。
-
class LogEvent:记录日志事件。主要记录一下信息
@param[in] logger 日志器 @param[in] level 日志级别 @param[in] file 文件名 @param[in] line 文件行号 @param[in] elapse 程序启动依赖的耗时(毫秒) @param[in] thread_id 线程id @param[in] fiber_id 协程id @param[in] time 日志时间
-
class LogEventWarp:日志事件包装器。将logEvent打包,可以直接通过使用该类完成对日志的定义。
-
class LogFormatter:日志格式化。
%m 消息 %p 日志级别 %r 累计毫秒数 %c 日志名称 %t 线程id %n 换行 %d 时间 %f 文件名 %l 行号 %T 制表符 %F 协程id %N 线程名称 默认格式 "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
-
class LogAppender:日志输出目标。有两个子类 class StdoutLogAppender 和 class FileLogAppender,可以分别输出到控制台和文件
-
class LoggerManager:日志管理器。单列模式
详解
class LogLevel(日志级别)
通过枚举定义了6个级别
enum Level{
// 未知 级别
UNKNOW = 0,
// DEBUG 级别
DEBUG = 1,
// INFO 级别
INFO = 2,
// WARN 级别
WARN = 3,
// ERROR 级别
ERROR = 4,
// FATAL 级别
FATAL = 5
};
ToString(提供从日志级别 TO 文本的转换)
通过宏定义 XX 将不同的级别放入switch case语句中。宏定义中, #name = "name"
XX(DEBUG)等价于
case LogLevel::DEBUG:
return "DEBUG";
break;
const char* LogLevel::ToString(LogLevel::Level level){
switch (level){
#define XX(name) \
case LogLevel::name: \
return #name; \
break;
XX(DEBUG);
XX(INFO);
XX(WARN);
XX(ERROR);
XX(FATAL);
#undef XX
default:
return "UNKNOW";
}
return "UNKNOW";
}
FromString(提供从文本 To 日志级别的转换)
转换时不针对大小写,DEBUG和debug都可以完成对应的转化
LogLevel::Level LogLevel::FromString(const std::string &str) {
#define XX(level, v) \
if(str == #v) { \
return LogLevel::level; \
}
XX(DEBUG, debug);
XX(INFO, info);
XX(WARN, warn);
XX(ERROR, error);
XX(FATAL, fatal);
XX(DEBUG, DEBUG);
XX(INFO, INFO);
XX(WARN, WARN);
XX(ERROR, ERROR);
XX(FATAL, FATAL);
return LogLevel::UNKNOW;
#undef XX
}
class LogEvent(日志事件)
mumber(成员变量)
const char* m_file = nullptr; //文件名
int32_t m_line = 0; //行号
uint32_t m_elapse = 0; //程序启动开始到现在的毫秒数
uint32_t m_thieadId = 0; //线程id
uint32_t m_fiberId = 0; //协程id
uint64_t m_time; //时间戳
std::string m_threadName; //线程名称
std::stringstream m_ss; //日志内容流
std::shared_ptr<Logger> m_logger; //日志器
LogLevel::Level m_level; //日志等级
LogEvent(构造函数 初始化所有事件)
/**
* @brief 构造函数
*
* @param[in] logger 日志器
* @param[in] level 日志级别
* @param[in] file 文件名
* @param[in] line 文件行号
* @param[in] elapse 程序启动依赖的耗时(毫秒)
* @param[in] thread_id 线程id
* @param[in] fiber_id 协程id
* @param[in] time 日志时间
* @param[in] thread_name 线程名称
*/
LogEvent::LogEvent(std::shared_ptr<Logger> logger, LogLevel::Level level
, const char* file, int32_t line, uint32_t elapse
, uint32_t thread_id, uint32_t fiber_id, uint64_t time
, const std::string& thread_name)
:m_file(file)
,m_line(line)
,m_elapse(elapse)
,m_thieadId(thread_id)
,m_fiberId(fiber_id)
,m_time(time)
,m_threadName(thread_name)
,m_logger(logger)
,m_level(level) {
}
format(格式化写入日志内容)
这里代码没被使用,方法还是可以看一下
1)首先在函数里定义一具va_list
型的变量al
,这个变量是指向参数的指。
2)使用 va_start(al, fmt)
宏初始化 al
, 并将其指向参数列表中的第一个参数。
3)将fmt
和al
传入format
中。
4)使用vasprintf(&buf, fmt, al)
将 al
按照fmt格式
放到buf
中。
5)若成功,则将buf
输出到流中 。
6)最后使用 va_end()
宏清理 va_list
。
void LogEvent::format(const char* fmt, ...) {
va_list al; //1)
va_start(al, fmt); //2)
format(fmt, al); //3)
va_end(al); //6)
}
void LogEvent::format(const char* fmt, va_list al){
char *buf = nullptr;
// len返回写入buf的长度
int len = vasprintf(&buf, fmt, al); //4)
if(len != -1) {
m_ss << std::string(buf, len); //5)
free(buf);
}
}
class LogEventWarp(日志事件包装器)
mumber(成员变量)
// 日志事件
LogEvent::ptr m_event;
LogEventWarp(构造函数)
初始化要包装的LogEvent
LogEventWarp::LogEventWarp(LogEvent::ptr e)
:m_event(e){
}
~LogEventWarp(析构函数)
在析构的时候将LogEvent写到Logger里面
LogEventWarp::~LogEventWarp() {
m_event->getLogger()->log(m_event->getLevel(), m_event);
}
在此说一下使用日志的宏,这里定义了SYLAR_LOG_LEVEL
宏,用来输出Level
级别的LogEvent
,并将LogEvent
写入到Logger
中。
这里也说明了为什么要使用LogEventWarp
这个类。当想使用该宏打一次日志后,由于LogEvent使用的是智能指针,在定义该宏的作用域下这个LogEvent
并不会立即释放,所以使用LogEventWarp
包装LogEvent::ptr
当定义该宏的语句执行完后就会自动进入析构函数,并将LogEvent
写入Logger
中,打印出日志。
#define SYLAR_LOG_LEVEL(logger, level) \
if (logger->getLevel() <= level) \
sylar::LogEventWarp(sylar::LogEvent::ptr (new sylar::LogEvent(logger, level, \
__FILE__, __LINE__, 0, sylar::GetThreadId(), \
sylar::GetFiberId(), time(0), sylar::Thread::GetName()))).getSS()
#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
class LogFormatter & class FormatItem
class FormatItem(日志内容格式化)
该类为LogFormatter
的public内部类成员,通过该类得到解析后的格式。
此类为抽象类,不同事件的子类继承该类,并且重写纯虚函数format
将日志格式转化到流
格式化日志到流
// 消息format
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();
}
};
// 日志级别format
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);
}
};
// 执行时间format
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();
}
};
// 日志器名称format
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 << event->getLogger()->getName();
}
};
// 线程id format
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->getThieadId();
}
};
// 协程id format
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();
}
};
// 线程名称format
class ThreadNameFormatItem : public LogFormatter::FormatItem{
public:
ThreadNameFormatItem(const std::string& str = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) override {
os << event->getThreadName();
}
};
// 时间format
class DateTimeFormatItem : public LogFormatter::FormatItem{
public:
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 {
struct tm tm;
time_t time = event->getTime(); //创建event时默认给的 time(0) 当前时间戳
localtime_r(&time, &tm); //将给定时间戳转换为本地时间,并将结果存储在tm中
char buf[64];
strftime(buf, sizeof(buf), m_format.c_str(), &tm); //将tm格式化为m_format格式,并存储到buf中
os << buf;
}
private:
std::string m_format;
};
class LogFormatter(日志格式化)
mumber(成员变量)
// 日志格式模板
std::string m_pattern;
// 日志格式解析后格式
std::vector<FormatItem::ptr> m_items;
// 判断日志格式错误
bool m_error = false;
LogFormatter(构造函数)
初始化日志格式,并解析
LogFormatter::LogFormatter(const std::string& pattern)
:m_pattern(pattern) {
init();
}
format(返回格式化日志文本)
将解析后的日志信息输出到流中
std::string LogFormatter::format (std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event){
std::stringstream ss;
for(auto& i : m_items) {
i->format(ss, logger, level, event);
}
return ss.str();
}
init(解析格式)
得到相应FormatItem
放入m_items
默认格式模板为:"%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
e.g.Y-M-D H:M:S threadId threadName fiberId [Level] [logName] FILE:LINE message
//%xxx %xxx{xxx} %%
// m_pattern "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
void LogFormatter::init(){
//string, 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]); //在nstr后面添加一个该字符
continue;
}
if((i + 1) < m_pattern.size()) { //保证m_pattern不越界
if (m_pattern[i + 1] == '%') { //解析 "%%"
nstr.append(1, '%'); //在nstr后面加上%
continue;
}
size_t n = i + 1; //遇到'%'往下 (e.g.) n = 1, m_pattern[1] = 'd'
int fmt_status = 0; //状态1: 解析时间{%Y-%m-%d %H:%M:%S} 状态0:解析之后的
size_t fmt_begin = 0; //开始位置 为{
std::string str; //d T t N等格式
std::string fmt; //保存时间格式 %Y-%m-%d %H:%M:%S
while(n < m_pattern.size()){
// fmt_status != 0, m_attern[n]不是字母,m_pattern[n]不是'{', m_pattern[n]不是'}'
// (e.g.) %T% (i -> %, n -> T, while循环 n -> % 此时解析完一个T, break
// (e.g.) 遇到 [ ] break,取出[%p]中的p
if(!fmt_status && (!isalpha(m_pattern[n]) && m_pattern[n] != '{' //返回0表示该字符不是字母字符。
&& 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); //str = "d"
fmt_status = 1;
fmt_begin = n;
++n;
continue;
}
} else if(fmt_status == 1) { //结束解析时间格式
if(m_pattern[n] == '}') {
// fmt = %Y-%m-%d %H:%M:%S
fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1);
fmt_status = 0;
++n;
break; //解析时间结束break
}
}
++n;
if (n == m_pattern.size()) { //最后一个字符
if (str.empty()) {
str = m_pattern.substr(i + 1);
}
}
}
if(fmt_status == 0{
if(!nstr.empty()){ // nstr: [ :
vec.push_back(std::make_tuple(nstr, std::string(), 0)); // 将[ ]放入, type为0
nstr.clear();
}
vec.push_back(std::make_tuple(str, fmt, 1)); //(e.g.) ("d", %Y-%m-%d %H:%M:%S, 1) type为1
i = n - 1; //跳过已解析的字符,让i指向当前处理的字符,下个for循环会++i处理下个字符
} else if(fmt_status == 1) {
std::cout << "Pattern parde 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)); //(e.g.) 最后一个字符为[ ] :
}
// map类型为<string, cb>, string为相应的日志格式, cb返回相应的FormatItem智能指针
static std::map<std::string, std::function<FormatItem::ptr(const std::string& fmt)> > 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) { //若type为0
//将解析出的FormatItem放到m_items中 [ ] :
m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(i))));
} else { //type为1
auto it = s_format_items.find(std::get<0>(i)); //从map中找到相应的FormatItem
if(it == s_format_items.end()) { //若没有找到则用StringFormatItem显示错误信息 并设置错误标志位
m_items.push_back(FormatItem::ptr(new StringFormatItem("<<error_format %" + std::get<0>(i) + ">>")));
m_error = true;
} else { //返回相应格式的FormatItem,其中std::get<1>(i)作为cb的参数
m_items.push_back(it->second(std::get<1>(i)));
}
}
}
}
class LogAppender & class StdoutLogAppender & class FileLogAppender
class LogAppender(日志输出目标)
class LogAppender
是抽象类,有两个子类,分别为StdoutLogAppender
和FileLogAppender
,分别实现控制台和文件的输出。两个类都重写纯虚函数log
方法实现写入日志,重写纯虚函数toYamlString
方法实现将日志转化为YAML
格式的字符串
mumber(成员变量)
//日志级别
LogLevel::Level m_level = LogLevel::DEBUG;
//日志格式器
LogFormatter::ptr m_formatter;
// 互斥锁
MutexType m_mutex;
// 是否有formatter
bool m_hasFormatter = false;
setFormatter(更改日志格式器)
void LogAppender::setFormatter(LogFormatter::ptr val) {
MutexType::Lock lock(m_mutex);
m_formatter = val;
if (m_formatter) {
m_hasFormatter = true;
} else {
m_hasFormatter = false;
}
}
getFormatter(获得日志格式器)
LogFormatter::ptr LogAppender::getFormatter() {
MutexType::Lock lock(m_mutex);
return m_formatter;
}
class StdoutLogAppender(输出到控制台的Appender)
class StdoutLogAppender : public LogAppender {
public:
typedef std::shared_ptr<StdoutLogAppender> ptr;
void log(Logger::ptr logger, LogLevel::Level level, LogEvent::ptr event) override;
std::string toYamlString() override;
};
log(输出到流)
void StdoutLogAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event){
if(level >= m_level) {
MutexType::Lock lock(m_mutex);
std::cout << m_formatter->format(logger, level, event); //这里调用Logformat的format,它会遍历m_items调用相应的format输出到流
}
}
toYamlString(输出Yaml
格式字符串)
std::string StdoutLogAppender::toYamlString() {
MutexType::Lock lock(m_mutex);
YAML::Node node;
node["type"] = "StdoutLogAppender";
if(m_level != LogLevel::UNKNOW) {
node["level"] = LogLevel::ToString(m_level);
}
if(m_hasFormatter && m_formatter) {
node["formatter"] = m_formatter->getPattern();
}
std::stringstream ss;
ss << node;
return ss.str();
}
class FileLogAppender(输出到文件的Appender)
mumber(成员变量)
// 文件路径
std::string m_filename;
// 文件流
std::ofstream m_filestream;
// 每秒reopen一次,判断文件有没有被删
uint64_t m_lastTime = 0;
FileLogAppender(构造函数)
初始化文件路径,并且打开文件
FileLogAppender::FileLogAppender(const std::string& filename)
:m_filename(filename){
reopen();
}
reopen(写入文件)
bool FileLogAppender::reopen(){
MutexType::Lock lock(m_mutex);
if (m_filestream){
m_filestream.close();
}
m_filestream.open(m_filename, std::ios::app); //以追加的方式写入文件中
return !!m_filestream;
}
log(输出到文件)
重写log
方法,输出到文件
void FileLogAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event){
if (level >= m_level){
uint64_t now = time(0);
if (now != m_lastTime) { //每秒重新reopen
reopen();
m_lastTime = now;
}
MutexType::Lock lock(m_mutex);
if (!(m_filestream << m_formatter->format(logger, level, event))) { //写到m_filestream流中
std::cout << "error" << std::endl;
}
}
}
toYamlString(转化为YAML格式字符串)
重写toYamlString
方法,转化为YAML格式字符串
std::string FileLogAppender::toYamlString() {
MutexType::Lock lock(m_mutex);
YAML::Node node;
node["type"] = "FileLogAppender";
node["file"] = m_filename;
if(m_level != LogLevel::UNKNOW) {
node["level"] = LogLevel::ToString(m_level);
}
if(m_hasFormatter && m_formatter) {
node["formatter"] = m_formatter->getPattern();
}
std::stringstream ss;
ss << node;
return ss.str();
}
class Logger(日志器)
mumber(成员变量
//日志名称
std::string m_name;
//日志级别
LogLevel::Level m_level;
// 互斥锁
MutexType m_mutex;
// 日志目标集合
std::list<LogAppender::ptr> m_appenders;
//日志格式器
LogFormatter::ptr m_formatter;
// root Log
Logger::ptr m_root;
Logger(构造函数)
名称,def = root
日志级别, def = DEBUG
日志格式, def = "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
Logger::Logger(const std::string& name)
:m_name(name)
,m_level(LogLevel::DEBUG){
m_formatter.reset(new LogFormatter("%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"));
}
log(写不同Level日志到日志目标)
m_appenders为日志目标地,将当前logger输出到相应的appender,因为Appender的log要传入logger的智能指针,所以使用shared_from_this()获得当前logger的智能指针
void Logger::log(LogLevel::Level level, LogEvent::ptr event){
if (level >= m_level){
auto self = shared_from_this();
MutexType::Lock lock(m_mutex);
if (!m_appenders.empty()) {
for(auto& i : m_appenders){
i->log(self, level, event);
}
} else if(m_root) { //当logger的appenders为空时,使用root写logger
m_root->log(level, event);
}
}
}
addAppender(添加日志目标)
若appender没有formatter的话就将默认formatter赋给他,若有formatter则直接添加到m_appenders队列中
void Logger::addAppender(LogAppender::ptr appender){
MutexType::Lock lock(m_mutex);
if (!appender->getFormatter()) {
MutexType::Lock ll(appender->m_mutex);
appender->m_formatter = m_formatter;
}
m_appenders.push_back(appender);
}
delAppender(删除日志目标)
在m_appenders中找到要删除的appender,erase掉
void Logger::delAppender(LogAppender::ptr appender){
MutexType::Lock lock(m_mutex);
for (auto it = m_appenders.begin();
it != m_appenders.end(); ++it) {
if(*it == appender) {
m_appenders.erase(it);
break;
}
}
}
setFormatter(通过智能指针 )
将新的formatter赋给m_formatter,若appender没有formatter,则将appender的formatter更新。
void Logger::setFormatter(LogFormatter::ptr val){
MutexType::Lock lock(m_mutex);
m_formatter = val;
for (auto& i : m_appenders) {
MutexType::Lock ll(i->m_mutex);
if (!i->m_hasFormatter) {
i->m_formatter = m_formatter;
}
}
}
setFormatter(通过字符串)
new一个新的formatter,若格式没错,调用上面的setFormatter设置Formatter。
void Logger::setFormatter(const std::string &val){
sylar::LogFormatter::ptr new_val(new sylar::LogFormatter(val));
if (new_val->isError()) {
std::cout << "Logger setFormatter name = " << m_name
<< "value = " << val << "invalid formatter"
<< std::endl;
return;
}
// m_formatter = new_val;
setFormatter(new_val);
}
toYamlString(转换为YAML格式输出)
将当前logger name,level,formatter,appenders YAML格式按流输出
std::string Logger::toYamlString() {
MutexType::Lock lock(m_mutex);
YAML::Node node;
node["name"] = m_name;
if(m_level != LogLevel::UNKNOW) {
node["level"] = LogLevel::ToString(m_level);
}
if (m_formatter) {
node["formatter"] = m_formatter->getPattern();
}
for (auto& i : m_appenders) {
node["appenders"].push_back(YAML::Load(i->toYamlString()));
}
std::stringstream ss;
ss << node;
return ss.str();
}
class LoggerManager(日志管理器)
使用单列模式管理日志管理器,都要通过LoggerMgr
来获得logger
typedef sylar::Singleton<LoggerManager> LoggerMgr;
mumber(成员变量)
// 互斥锁
MutexType m_mutex;
// 日志器容器
std::map<std::string, Logger::ptr> m_loggers;
// 主日志器
Logger::ptr m_root;
LoggerManager(构造函数)
LoggerManager::LoggerManager() {
m_root.reset(new Logger);
m_root->addAppender(LogAppender::ptr(new StdoutLogAppender));
m_loggers[m_root->m_name] = m_root;
}
getLogger(获取日志器)
在map中找到相应的logger
就返回他,若没有就创建一个logger
并将他放到日志器容器m_loggers
中,再返回他
Logger::ptr LoggerManager::getLogger(const std::string& name) {
MutexType::Lock lock(m_mutex);
auto it = m_loggers.find(name);
if (it != m_loggers.end()) {
return it->second;
}
Logger::ptr logger(new Logger(name));
logger->m_root = m_root; //将logger的root赋值,当没有appender时,使用root写logger
m_loggers[name] = logger;
return logger;
}
toYamlString(将日志格式转化为YAML字符串)
std::string LoggerManager::toYamlString() {
MutexType::Lock lock(m_mutex);
YAML::Node node;
for (auto& i : m_loggers) {
node.push_back(YAML::Load(i.second->toYamlString()));
}
std::stringstream ss;
ss << node;
return ss.str();
}
宏定义
使用流的方式,将不同日志级别的事件写入logger中
#define SYLAR_LOG_LEVEL(logger, level) \
if (logger->getLevel() <= level) \
sylar::LogEventWarp(sylar::LogEvent::ptr (new sylar::LogEvent(logger, level, \
__FILE__, __LINE__, 0, sylar::GetThreadId(), \
sylar::GetFiberId(), time(0), sylar::Thread::GetName()))).getSS()
#define SYLAR_LOG_DEBUG(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::DEBUG)
#define SYLAR_LOG_INFO(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::INFO)
#define SYLAR_LOG_WARN(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::WARN)
#define SYLAR_LOG_ERROR(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::ERROR)
#define SYLAR_LOG_FATAL(logger) SYLAR_LOG_LEVEL(logger, sylar::LogLevel::FATAL)
使用格式化方式, 将不同日志级别的事件写入logger中
#define SYLARY_LOG_FMT_LEVEL(logger, level, fmt, ...) \
if (logger->getLevel() <= level) \
sylar::LogEventWarp(sylar::LogEvent::ptr(new sylar::LogEvent(logger, level, \
__FILE__, __LINE__, 0, sylar::GetThreadId(), \
sylar::GetFiberId(), time(0), sylar::Thread::GetName()))).getEvent()->format(fmt, __VA_ARGS__)
#define SYLARY_LOG_FMT_DEBUG(logger, fmt, ...) SYLARY_LOG_FMT_LEVEL(logger, sylar::LogLevel::DEBUG, fmt, __VA_ARGS__)
#define SYLARY_LOG_FMT_INFO(logger, fmt, ...) SYLARY_LOG_FMT_LEVEL(logger, sylar::LogLevel::INFO, fmt, __VA_ARGS__)
#define SYLARY_LOG_FMT_WARN(logger, fmt, ...) SYLARY_LOG_FMT_LEVEL(logger, sylar::LogLevel::WARN, fmt, __VA_ARGS__)
#define SYLARY_LOG_FMT_ERROR(logger, fmt, ...) SYLARY_LOG_FMT_LEVEL(logger, sylar::LogLevel::ERROR, fmt, __VA_ARGS__)
#define SYLARY_LOG_FMT_FATAL(logger, fmt, ...) SYLARY_LOG_FMT_LEVEL(logger, sylar::LogLevel::FATAL, fmt, __VA_ARGS__)
获得主日志器
#define SYLAR_LOG_ROOT() sylar::LoggerMgr::GetInstance()->getRoot()
获得相应名字的日志器
#define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger(name)
总结
这个模块看起来没有那么多的知识点需要掌握,但是非常考察我们对C++基础知识的掌握,每个类之间的耦合度非常高,还有部分的代码并没有给大家详细的列出来解释,但都是很简单的代码了,大部分核心的代码都已经进行详细的解释。
可以看到,sylar在写代码时使用了大量的宏定义,这个谁用谁知道啊,在后面的项目过程中,也大量使用了宏定义,让代码更加的简洁。
日志管理使用单例模式,保证从容器m_loggers
中拿出的日志器是唯一不变的。日志管理器会初始化一个主日志器放到容器中,若再创建一个新的日志器时没有设置appender
,则会使用这个主日志器进行日志的输出,但是输出时日志名称并不是主日志器的,因为在输出时是按照event
中的logger
的名称输出的。
最后在这里给大家再梳理一遍当一个日志器被定义再到打印出日志是怎么一个流程,可以边看代码边看,希望可以帮助大家更好的理解日志模块的代码。
- 首先我们使用日志管理器
LoggerMgr
获得一个logger
,例如这里获得主日志器,可以使用宏SYLAR_LOG_ROOT()
获得,例如sylar::Logger::ptr g_logger = SYLAR_LOG_ROOT()
。此时LoggerManager
会new
一个新的Logger
,默认为名字为root
,level
为DEBUG
,formatter
为%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n
。并且addAppend()
为StdoutLogAppender
,然后将root logger
放入日志器容器中。此时,一个日志器就初始化好了。 - 在过程1中,在初始化
g_logger
时,它的formatter
也会被init()
方法初始化,并将解析对应的FormatItem
按照格式顺序放到m_items
中。在也会将StdoutLogAppender
加到m_appenders
中,在addAppender()
时,若appender
没有formatter
时,会将g_logger
的formatter
赋给它。
- 当我们想打印日志时,需要创建相应日志级别的
LogEvent
,其中包含了时间 线程号 线程名称 协程号 [日志级别] [日志器名称] 文件名:行号 消息
(e.g.)2023-04-26 15:12:12 3613 iom 0 [INFO] [root] tests/test_hook.cc:69 hello world
-
使用宏定义日志级别为
INFO
的事件SYLAR_LOG_INFO(g_logger)
,使用方法如下SYLAR_LOG_INFO(logger) << hello world
。在宏的最后调用getSS()
获得消息字符,将"hello world"
保存到m_ss
中。 -
由于
LogEvent
是由LogEventWarp
包装起来的,当该行结束时,自动执行析构函数语句m_event->getLogger()->log(m_event->getLevel(), m_event);
其中
getLogger()
获得当前日志器g_logger
,并且调用g_logger->log(m_event->getLevel(), m_event)
方法,在此方法中会从m_appenders
中循环拿出要输出到的目的地,因为这里只有一个StdoutLogAppender
,所以调用StdoutLogAppender::ptr->log(self, level, event)
方法,其中调用m_formatter->format(*logger*, *level*, *event*)
遍历formatter
的m_item
将event
中的事件按照格式顺序输出到流,然后用std::cout
打印到控制台,此时日志信息就按照流的方式全部打印出来了。