介绍日志模块,主要包括日志级别、日志格式化、日志输出、日志分类等功能。
1 日志模块概述
日志对于一个库或者框架来说是非常重要的,它可以帮助开发者了解程序的运行状态,排查问题,优化性能等。具体来说,日志模块用于格式化输出程序日志,方便从日志中定位程序运行过程中出现的问题。这里的日志除了日志内容本身之外,还应该包括文件名/行号,时间戳,线程/协程号,模块名称,日志级别等额外信息,甚至在打印致命的日志时,还应该附加程序的栈回溯信息,以便于分析和排查问题。
一个完善的日志模块应该具备以下特点:
- 多日志级别:如
DEBUG
、INFO
、WARN
、ERROR
、FATAL
等,用于区分日志的重要性和严重性。
- 日志格式化:支持格式化输出,如时间戳、文件名/行号、线程/协程号、模块名称等,并可灵活配置。
- 日志输出:支持输出到控制台、文件、网络等不同的目的地,或者同时输出到多个目的地。
- 日志分类:日志可以分类并命名,一个程序的各个模块可以使用不同的名称来输出日志,方便定位问题。
- 日志可配置:可以通过配置文件或者命令行参数来配置。
sylar日志模块类似于log4cplus
日志库,提供了多日志级别、日志格式化、日志输出、日志分类等功能。
主要有以下几个类:
Logger
:日志器,用于定义日志级别、日志格式、日志输出地。
class LogLevel
:定义日志级别,并提供将日志级别与文本之间的互相转化
LogEvent
:记录日志事件,主要记录一下信息。
LogFormatter
:日志格式化器,用于格式化日志输出。
LogEventWrap
:日志事件包装器,用于将日志事件和日志器绑定在一起。
LogAppender
:日志输出器,用于将日志事件输出到指定的地方。
LogManager
:日志管理器,用于管理日志器和日志输出器,采用单例模式。
2 LogLevel
类
通过枚举定义了9个日志级别
1 2 3 4 5 6 7 8 9 10 11
| enum Level { FATAL = 0, ALERT = 1, CRIT = 2, ERROR = 3, WARN = 4, NOTICE = 5, INFO = 6, DEBUG = 7, NOTSET = 8 };
|
2.1 成员函数
2.1.1 ToString
将日志级别转换为字符串,函数声明static const char* ToString(LogLevel::Level level);
具体实现通过宏定义函数XX
,使用switch
语句将日志级别转换为字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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"; }
|
2.1.2 FromString
将字符串转换为日志级别,转换时不针对大小写,DEBUG和debug都可以完成对应的转化。通过宏定义函数XX
,将字符串与日志级别进行比较,返回对应的日志级别。如果没有匹配的日志级别,则返回NOTSET
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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; }
|
3 LogEvent
类
日志事件,用于记录日志现场,比如该日志的级别,文件名/行号,日志消息,线程/协程号,所属日志器名称等,有以下成员变量:
1 2 3 4 5 6 7 8 9 10
| LogLevel::Level m_level; std::stringstream m_ss; const char *m_file = nullptr; int32_t m_line = 0; int64_t m_elapse = 0; uint32_t m_threadId = 0; uint64_t m_fiberId = 0; time_t m_time; std::string m_threadName; std::string m_loggerName;
|
构造函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
LogEvent::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) : m_level(level) , m_file(file) , m_line(line) , m_elapse(elapse) , m_threadId(thread_id) , m_fiberId(fiber_id) , m_time(time) , m_threadName(thread_name) , m_loggerName(logger_name) { }
|
4 LogEventWrap
类
LogEventWrap
类用于将LogEvent
和Logger
绑定在一起,因为一条日志只会在一个日志器上进行输出。将日志事件和日志器包装到一起后,方便通过宏定义来简化日志模块的使用。另外,LogEventWrap
还负责在构建时指定日志事件和日志器,在析构时调用日志器的log
方法将日志事件进行输出。
成员变量如下:
1 2
| Logger::ptr m_logger; LogEvent::ptr m_event;
|
4.1 成员函数
4.1.1 LogEventWrap()
构造函数:
1 2 3 4
| LogEventWrap::LogEventWrap(Logger::ptr Logger, LogEvent::ptr event) : m_logger(Logger) , m_event(event) { }
|
4.1.2 ~LogEventWrap()
析构函数:
1 2 3 4
| LogEventWrap::~LogEventWrap() { m_logger -> log(m_event); }
|
在这里说一下,Sylar使用的日志的宏,log.h
头文件定义了一系列的宏,其中SYLAR_LOG_LEVEL
是一个通用的宏,用来输出Level
级别的LogEvent
,并将LogEvent
写入到Logger
中。
当使用该宏打印一次日志后,由于LogEvent
使用的是智能指针,在定义该宏的作用域下这个LogEvent
并不会立即释放,所以使用LogEventWarp
包装LogEvent::ptr
当定义该宏的语句执行完后就会自动进入析构函数,并将LogEvent
写入Logger
中,打印出日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ‵‵‵cpp #define SYLAR_LOG_LEVEL(logger, level) \ if(logger -> getLevel() >= level) \ 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)
|
FormatItem
类为LogFormatter
的public
内部类成员,通过该类得到解析后的格式。 是一个抽象类,不同事件的子类继承该类,并且重写纯虚函数format
将日志格式转化到std::ostream
中。
格式化项的子类有:
MessageFormatItem
:日志内容格式化项
LevelFormatItem
:日志级别格式化项
ElapseFormatItem
:日志耗时格式化项
LoggerNameFormatItem
:日志器名称格式化项
ThreadIdFormatItem
:线程id格式化项
FiberIdFormatItem
:协程id格式化项
ThreadNameFormatItem
:线程名称格式化项
DateTimeFormatItem
:日期时间格式化项
FilenameFormatItem
:文件名格式化项
LineFormatItem
:行号格式化项
NewLineFormatItem
:换行格式化项
StringFormatItem
:字符串格式化项
TabFormatItem
:Tab格式化项
PercentFormatItem
:%格式化项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
class MessageFormatItem : public LogFormatter::FormatItem { public: MessageFormatItem(const std::string& str) {} void format(std::ostream& os, LogEvent::ptr event) override { os << event -> getContent(); } };
class LevelFormatItem : public LogFormatter::FormatItem { public: LevelFormatItem(const std::string& str) {} void format(std::ostream& os, LogEvent::ptr event) override { os << LogLevel::ToString(event -> getLevel()); } };
class ElapseFormatItem : public LogFormatter::FormatItem { public: ElapseFormatItem(const std::string& str) {} void format(std::ostream& os, LogEvent::ptr event) override { os << event -> getElapse(); } };
class LoggerNameFormatItem : public LogFormatter::FormatItem { public: LoggerNameFormatItem(const std::string& str) {} void format(std::ostream& os, LogEvent::ptr event) override { os << event -> getLoggerName(); } };
class ThreadIdFormatItem : public LogFormatter::FormatItem { public: ThreadIdFormatItem(const std::string& str) {} void format(std::ostream& os, LogEvent::ptr event) override { os << event -> getThreadId(); } };
class FiberIdFormatItem : public LogFormatter::FormatItem { public: FiberIdFormatItem(const std::string& str) {} void format(std::ostream& os, LogEvent::ptr event) override { os << event -> getFiberId(); } };
class ThreadNameFormatItem : public LogFormatter::FormatItem { public: ThreadNameFormatItem(const std::string& str) {} void format(std::ostream& os, LogEvent::ptr event) override { os << event -> getThreadName(); } };
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, LogEvent::ptr event) override { 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; };
|
日志格式器,与log4cpp
的PatternLayout
对应,用于格式化一个日志事件。该类构建时可以指定pattern
,表示如何进行格式化。提供format
方法,用于将日志事件格式化成字符串。
主要有以下成员变量:
1 2 3
| std::string m_pattern; std::vector<FormatItem::ptr> m_items; bool m_error = false;
|
6.1 成员函数
构造函数,初始化日志格式模板和解析日志格式项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| LogFormatter::LogFormatter(const std::string& pattern) : m_pattern(pattern) { init(); } ``` ### 6.1.2 `format()`
格式化日志,将日志事件格式化输出到`std::ostream`中。
```cpp
std::string LogFormatter::format(LogEvent::ptr event) { std::stringstream ss; for(auto& i : m_items) i -> format(ss, event); return ss.str(); }
|
6.1.3 init()
初始化,解析格式模板,提取模板项,得到相应的FormatItem
,放入到m_items
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| void LogFormatter::init() { std::vector<std::pair<int, std::string>> patterns; std::string tmp; std::string dateformat; bool error = false; bool parsing_string = true;
size_t i = 0; while(i < m_pattern.size()) { std::string c = std::string(1, m_pattern[i]); if(c == "%") { if(parsing_string) { if(!tmp.empty()) patterns.push_back(std::make_pair(0, tmp)); tmp.clear(); parsing_string = false; i++; continue; } else { patterns.push_back(std::make_pair(1, c)); parsing_string = true; i++; continue; } } else { if(parsing_string) { tmp += c; i++; continue; } else { patterns.push_back(std::make_pair(1, c)); parsing_string = true;
if(c != "d") { i++; continue; } i++; if(i < m_pattern.size() && m_pattern[i] != '{') continue; i++; while(i < m_pattern.size() && m_pattern[i] != '}') { dateformat.push_back(m_pattern[i]); i++; } if(m_pattern[i] != '}') { std::cout << "[ERROR] LogFormatter::init() " << "pattern: [" << m_pattern << "] '{' not closed" << std::endl; error = true; break; } i++; continue; } } }
if(error) { m_error = true; return; }
if(!tmp.empty()) { patterns.push_back(std::make_pair(0, tmp)); tmp.clear(); }
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), XX(p, LevelFormatItem), XX(c, LoggerNameFormatItem), XX(r, ElapseFormatItem), XX(f, FileNameFormatItem), XX(l, LineFormatItem), XX(t, ThreadIdFormatItem), XX(F, FiberIdFormatItem), XX(N, ThreadNameFormatItem), XX(%, PercentFormatItem), XX(T, TabFormatItem), XX(n, NewLineFormatItem), #undef XX };
for(auto& v : patterns) { if( v.first == 0) m_items.push_back(FormatItem::ptr(new StringFormatItem(v.second))); else if(v.second == "d") m_items.push_back(FormatItem::ptr(new DateTimeFormatItem(dateformat))); else { auto it = s_format_items.find(v.second); if(it == s_format_items.end()) { std::cout << "[ERROR] LogFormatter::init() " << "pattern: [" << m_pattern << "] " << "unknown format item: " << v.second << std::endl; error = true; break; } else m_items.push_back(it -> second(v.second)); } }
if(error) { m_error = true; return; } }
|
假设给定的日志格式模板为 1
| %d{%Y-%m-%d %H:%M:%S} [%rms]%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n
|
则经过init()
函数解析后,得到的模板项patterns
如下:
1 2 3 4 5 6 7 8 9
| patterns = { {1, "d"}, {0, "["}, {1, "r"}, {0, "ms]"}, {1, "T"}, {1, "t"}, {1, "T"}, {1, "N"}, {1, "T"}, {1, "F"}, {1, "T"}, {0, "["}, {1, "p"}, {0, "]"}, {1, "T"}, {0, "["}, {1, "c"}, {0, "]"}, {1, "T"}, {1, "f"}, {0, ":"}, {1, "l"}, {1, "T"}, {1, "m"}, {1, "n"} }
|
代码主要逻辑
首先定义了一个存储解析出来的模板项的容器patterns
,每个模板项是一个pair
,第一个元素表示模板类型(0
表示普通字符串,1
表示需要格式化的项),第二个元素是模板内容。
使用tmp
变量临时存储常规字符串,dateformat
变量存储日期格式字符串,默认将%d
后面的大括号对里的全部字符都当作日期格式字符。
使用error
标志来标记解析是否出现错误。
使用parsing_string
标志来标记当前是否在解析普通字符串,初始为true
。
遍历日志格式模板,根据字符%
的出现情况分别处理普通字符串和模板字符。
如果遇到%
,则表示开始解析模板字符,如果在解析普通字符串时遇到%
,则说明一个模板项解析完毕,将其加入patterns
中。
如果遇到模板字符,则将其加入patterns
中,并根据模板字符类型特殊处理%d
,提取出日期格式字符串。
解析完成后,将剩余的常规字符也加入patterns
中。
定义一个存储模板项创建函数的静态映射表s_format_items
,将每个模板字符与相应的模板项创建函数关联起来。
根据解析出来的模板项,创建相应的FormatItem
并存储到m_items
中。
7 LogAppender
类
日志输出器,用于将一个日志事件输出到对应的输出地。
LogAppender
类是一个抽象类,用于派生了StdoutLogAppender
和FileLogAppender
两个子类,分别用于将日志输出到控制台和文件中。两个子类分别实现了LogAppender
类的log
方法实现写入日志,实现toYamlString
方法将日志输出器的配置转换为YAML
格式的字符串。
成员变量:
1 2 3
| MutexType m_mutex; LogFormatter::ptr m_formatter; LogFormatter::ptr m_defaultFormatter;
|
7.1 成员函数
设置日志格式化器。
1 2 3 4
| void LogAppender::setFormatter(LogFormatter::ptr val) { MutexType::Lock lock(m_mutex); m_formatter = val; }
|
获取日志格式化器,如果有设置则返回设置的格式化器,否则返回默认格式化器。
1 2 3 4
| LogFormatter::ptr LogAppender::getFormatter() { MutexType::Lock lock(m_mutex); return m_formatter ? m_formatter : m_defaultFormatter; }
|
7.2 派生类——StdoutLogAppender
类定义如下:
1 2 3 4 5 6 7
| class StdoutLogAppender : public LogAppender { public: typedef std::shared_ptr<StdoutLogAppender> ptr; StdoutLogAppender(); void log(LogEvent::ptr event) override; std::string toYamlString() override; };
|
7.2.1 log()
将日志事件输出到控制台,如果有设置格式化器,则使用设置的格式化器,否则使用默认格式化器。
1 2 3 4
| void StdoutLogAppender::log(LogEvent::ptr event) { if(m_formatter) m_formatter -> format(std::cout, event); else m_defaultFormatter -> format(std::cout, event); }
|
7.2.2 toYamlString()
将日志输出器的配置转换为YAML
格式的字符串。
1 2 3 4 5 6 7 8 9
| std::string StdoutLogAppender::toYamlString() { MutexType::Lock lock(m_mutex); YAML::Node node; node["type"] = "StdoutLogAppender"; node["pattern"] = m_formatter -> getPattern(); std::stringstream ss; ss << node; return ss.str(); }
|
7.3 派生类——FileLogAppender
输出到文件,类定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class FileLogAppender : public LogAppender { public: typedef std::shared_ptr<FileLogAppender> ptr; FileLogAppender(const std::string &file); void log(LogEvent::ptr event) override; bool reopen(); std::string toYamlString() override;
private: std::string m_filename; std::ofstream m_filestream; uint64_t m_lastTime = 0; bool m_reopenError = false; };
|
7.3.1 FileLogAppender()
构造函数,初始化文件路径,并打开
1 2 3 4 5
| FileLogAppender::FileLogAppender(const std::string& file) : LogAppender(LogFormatter::ptr(new LogFormatter)) { m_filename = file; reopen(); if(m_reopenError) std::cout << "reopen file " << m_filename << " error" << std::endl; }
|
7.3.2 reopen()
重新打开日志文件, 如果文件已经打开,先关闭文件,再打开文件。
1 2 3 4 5 6 7 8
| bool FileLogAppender::reopen() { MutexType::Lock lock(m_mutex); if(m_filestream) m_filestream.close(); m_filestream.open(m_filename, std::ios::app); m_reopenError = !m_filestream; return !m_reopenError; }
|
7.3.3 log()
重写了log
方法,将日志事件输出到文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void FileLogAppender::log(LogEvent::ptr event) { uint64_t now = event -> getTime(); if(now >= m_lastTime + 3) { reopen(); if(m_reopenError) std::cout << "reopen file " << m_filename << " error" << std::endl; m_lastTime = now; }
if(m_reopenError) return;
MutexType::Lock lock(m_mutex); if(m_formatter) { if(!m_formatter -> format(m_filestream, event)) std::cout << "[ERROR] FileLogAppender::log() " << "format error" << std::endl; } else { if(!m_defaultFormatter -> format(m_filestream, event)) std::cout << "[ERROR] FileLogAppender::log() " << "format error" << std::endl; } }
|
7.3.4 toYamlString()
重写toYamlString
方法,将日志输出器的配置转换为YAML
格式的字符串。
1 2 3 4 5 6 7 8 9 10
| std::string FileLogAppender::toYamlString() { MutexType::Lock lock(m_mutex); YAML::Node node; node["type"] = "FileLogAppender"; node["file"] = m_filename; node["pattern"] = m_formatter ? m_formatter -> getPattern() : m_defaultFormatter -> getPattern(); std::stringstream ss; ss << node; return ss.str(); }
|
8 Logger
类
日志器,负责进行日志输出。一个Logger
包含多个LogAppender
和一个日志级别,提供log
方法,传入日志事件,判断该日志事件的级别高于日志器本身的级别之后调用LogAppender
将日志进行输出,否则该日志被抛弃。
主要有以下成员变量:
1 2 3 4 5
| MutexType m_mutex; std::string m_name; LogLevel::Level m_level; std::list<LogAppender::ptr> m_appenders; uint64_t m_createTime;
|
8.1 Logger
类的成员函数
8.1.1 Logger()
构造函数,初始化日志器名称和日志级别。
1 2 3 4
| Logger::Logger(const std::string& name) : m_name(name) , m_level(LogLevel::INFO) , m_createTime(GetElapsedMS()) { }
|
8.1.2 log()
调用Logger
的所有appenders
将日志写一遍,Logger
至少要有一个appender
,否则没有输出
1 2 3 4 5 6
| void Logger::log(LogEvent::ptr event) { if(event -> getLevel() >= m_level) { MutexType::Lock lock(m_mutex); for(auto& i : m_appenders) i -> log(event); } }
|
8.1.3 addAppender()
添加日志输出器。
1 2 3 4
| void Logger::addAppender(LogAppender::ptr appender) { MutexType::Lock lock(m_mutex); m_appenders.push_back(appender); }
|
8.1.4 delAppender()
删除日志输出器,在m_appenders
中查找appender
,找到后删除。
1 2 3 4 5 6 7 8 9
| 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; } } }
|
8.1.5 toYamlString()
将当前Logger
的level
、name
、appenders
转换为YAML
格式的字符串。
1 2 3 4 5 6 7 8 9 10 11 12
| std::string Logger::toYamlString() { MutexType::Lock lock(m_mutex); YAML::Node node; node["name"] = m_name; node["level"] = LogLevel::ToString(m_level); for(auto& i : m_appenders) { node["appenders"].push_back(YAML::Load(i -> toYamlString())); } std::stringstream ss; ss << node; return ss.str(); }
|
9 LogManager
类
日志器管理类,单例模式,用于统一管理所有的日志器,提供日志器的创建与获取方法。LogManager
自带一个root Logger
,用于为日志模块提供一个初始可用的日志器。
1
| typedef sylar::Singleton<LoggerManager> LoggerMgr;
|
成员变量:
1 2 3
| MutexType m_mutex; std::map<std::string, Logger::ptr> m_loggers; Logger::ptr m_root;
|
9.1 成员函数
9.1.1 LoggerManager()
构造函数,初始化根日志器。
1 2 3 4 5 6
| LoggerManager::LoggerManager() { m_root.reset(new Logger); m_root -> addAppender(LogAppender::ptr(new StdoutLogAppender)); m_loggers[m_root -> getName()] = m_root; init(); }
|
9.1.2 getLogger()
获取日志器,如果指定名称的日志器未找到,那会就新创建一个,但是新创建的Logger
是不带Appender
的,需要手动添加Appender
1 2 3 4 5 6 7 8 9
| 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)); m_loggers[name] = logger; return logger; }
|
9.1.3 toYamlString()
将日志格式转化为YAML
格式的字符串。
1 2 3 4 5 6 7 8 9 10
| 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(); }
|
总结
虽然这个模块的知识点不多,但它确实考验了对C++基础知识的理解和应用。各个类之间的联系非常紧密,有些代码虽然没被详细解释,但它们都是相对简单的,关键部分的代码已经进行了详尽的阐述。
可以注意到,在编写代码时,频繁地使用了宏定义,在后续的项目开发中,宏定义同样被广泛采用,它让代码变得更加简洁。
在日志管理方面,采用了单例模式来确保从容器m_loggers
中获取的日志器是唯一的。
日志管理器首先会初始化一个主日志器并放入容器中。如果尝试创建一个新的日志器,并且没有为它指定一个附加器(appender)
,那么就会使用这个主日志器来输出日志。
值得注意的是,尽管使用的是主日志器进行输出,但输出时显示的日志名称并不是主日志器的名称,而是根据事件(event)
中指定的日志器名称来输出的。
总结一下日志模块的工作流程:
初始化LogFormatter
,LogAppender
, Logger
。
通过宏定义提供流式风格和格式化风格的日志接口。每次写日志时,通过宏自动生成对应的日志事件LogEvent
,并且将日志事件和日志器Logger
包装到一起,生成一个LogEventWrap
对象。
日志接口执行结束后,LogEventWrap
对象析构,在析构函数里调用Logger
的log方法将日志事件进行输出。