本文主要是介绍协程库项目—日志模块,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
日志模块程序结构图
sylarLog
├── LogLevel(日志级别封装类)
│ ├── 提供“从日志级别枚举值转换到字符串”、“从字符串转换相应的日志级别枚举值”等方法
├── LogEvent(日志事件类)
│ ├── 封装日志事件的属性,例如时间、线程id、日志等级、内容等等,并对外提供访问方法
│ └── 日志事件的构造在使用上会通过宏定义来简化
├── LogEventWrap(日志事件包装类)
│ ├── 内含日志事件 LogEvent
│ └── 日志事件在析构时由日志器进行输出
├── LogFormatter(日志格式类)
│ ├── 通过传递日志样式字符串给该类,该类对传入的字符串进行解析,例如 %d%t%p%m%n 表示 时间、线程号、日志等级、内容、换行
│ ├── 内含一个虚基类-日志内容格式化项 FormatItem
│ ├── 有13个子类,消息-MessageFormatItem、日志级别-LevelFormatItem、累计毫秒数-ElapseFormatItem、日志名称-NameFormatItem、线程id-ThreadIdFormatItem、换行-NewLineFormatItem、时间-DateTimeFormatItem、文件名-FilenameFormatItem、行号-LineFormatItem、Tab-TabFormatItem、协程id-FiberIdFormatItem、线程名称-ThreadNameFormatItem、直接打印字符串-StringFormatItem
│ └── 整个日志模块最复杂的逻辑就是该类解析日志样式的函数 init()
├── LogAppender(日志输出目的地类)
│ ├── LogAppender 为虚基类,有纯虚函数,留给子类去各自实现
│ ├── 实现的子类如 StdoutLogAppender 和 FileLogAppender
│ └── Appender 自带一个默认的 LogFormatter,以默认方式输出
├── StdoutLogAppender(标准化输出类)
│ └── 日志输出到控制台
├── FileLogAppender(文件输出类)
│ └── 日志输出到相应的文件中
├── Logger(日志器类)
│ ├── 设置日志名称、设置日志等级 LogLevel、设置日志输出位置 LogAppender、设置日志格式、根据日志级别控制日志输出等
├── LoggerManager(日志管理器类)
│ ├── 利用 map 存放各个 Logger 实例,其中 key 是日志器的名称,value 是日志器的智能指针
│ └── 还内含一个主日志器 root
└── 其他说明├── 每个类都 typedef std::shared_ptr ptr,方便外界使用其智能指针└── 普遍使用 Spinlock 实现互斥,保证线程安全。Spinlock 比 普通的 Mutex 效率高,但耗CPU。
数据流转
- 首先通过SYLAR_LOG_NAME(name)宏从LoggerMgr中获取对应的Logger对象,然后通过SYLAR_LOG_DEBUG(logger)->SYLAR_LOG_LEVEL(logger,level)宏创建一个新的LogEvent对象,并将其传递给LogEventWrap临时对象。接着,通过std::stringstream将日志内容存入其中。
- 当LogEventWrap临时对象析构时,会调用Logger的log方法,遍历其所有的LogAppender,并调用每个LogAppender的log方法(传入event参数)。
- 这里以FileLogAppender为例,LogAppender的log方法会加上自己的std::ostream参数(如果是输出到控制台,则是std::cout),然后调用LogFormatter的format方法(传入ostream、event参数)。
- LogFormatter的format方法会遍历自己缓存的所有FormatItem(继承了FormatItem的各种子类智能指针),将日志内容格式化(例如加上时间日期、线程id等)。
- 调用的是每个FormatItem的format方法(传入ostream、event参数)。最后,每个FormatItem的format方法会将格式化后的内容以流式方式存入std::ostream。如果是输出到控制台,那么这里就直接输出了。如果是文件,因为std::ostream关联了文件,因此会对文件进行缓存写(非实时写)。
LogFormatter类的init方法
LogFormatter类的init方法,用于解析日志格式字符串。主要功能如下:
首先,定义了一个patterns向量,用于存储解析到的模式项。每个模式项包括一个整数类型和一个字符串,类型为0表示该模式是常规字符串,为1表示该模式需要转义。
定义了一个临时变量tmp,用于存储常规字符串。
定义了一个日期格式字符串dateformat,默认把位于%d后面的大括号对里的全部字符都当作格式字符,不校验格式是否合法。
定义了一个布尔变量error,用于标记解析过程中是否出错。
定义了两个布尔变量parsing_string和parsing_pattern,分别表示是否正在解析常规字符和模板字符。初始时,parsing_string为true。
使用一个循环遍历m_pattern字符串中的每个字符,根据不同的情况进行解析。
如果当前字符是"%“,则根据parsing_string的值进行不同的处理。如果正在解析常规字符,则将之前的常规字符串添加到patterns中,并将parsing_string设置为false;如果正在解析模板字符,则将当前的”%“作为模板字符添加到patterns中,并将parsing_string设置为true。
如果当前字符不是”%“,则根据parsing_string的值进行不同的处理。如果正在解析常规字符,则将当前字符添加到tmp中;如果正在解析模板字符,则将当前字符作为模板字符添加到patterns中,并根据不同情况进行特殊处理。
在解析模板字符的过程中,如果遇到”%d",则需要进一步解析日期格式字符串。通过遍历后续字符,直到找到闭合的大括号,将其中的字符添加到dateformat中。
如果在解析过程中出现错误,将m_error设置为true并返回。
最后,根据解析得到的模式项创建相应的格式化项对象,并将其添加到m_items中。
如果解析过程中出现错误,将m_error设置为true并返回。
void LogFormatter::init() {// 按顺序存储解析到的pattern项// 每个pattern包括一个整数类型和一个字符串,类型为0表示该pattern是常规字符串,为1表示该pattern需要转义// 日期格式单独用下面的dataformat存储std::vector<std::pair<int, std::string>> patterns;// 临时存储常规字符串std::string tmp;// 日期格式字符串,默认把位于%d后面的大括号对里的全部字符都当作格式字符,不校验格式是否合法std::string dateformat;// 是否解析出错bool error = false;// 是否正在解析常规字符,初始时为truebool parsing_string = true;// 是否正在解析模板字符,%后面的是模板字符// bool parsing_pattern = false;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; // 在解析常规字符时遇到%,表示开始解析模板字符// parsing_pattern = true;i++;continue;} else {patterns.push_back(std::make_pair(1, c));parsing_string = true; // 在解析模板字符时遇到%,表示这里是一个%转义// parsing_pattern = false;i++;continue;}} else { // not %if(parsing_string) { // 持续解析常规字符直到遇到%,解析出的字符串作为一个常规字符串加入patternstmp += c;i++;continue;} else { // 模板字符,直接添加到patterns中,添加完成后,状态变为解析常规字符,%d特殊处理patterns.push_back(std::make_pair(1, c));parsing_string = true; // parsing_pattern = false;// 后面是对%d的特殊处理,如果%d后面直接跟了一对大括号,那么把大括号里面的内容提取出来作为dateformatif(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] != '}') {// %d后面的大括号没有闭合,直接报错std::cout << "[ERROR] LogFormatter::init() " << "pattern: [" << m_pattern << "] '{' not closed" << std::endl;error = true;break;}i++;continue;}}} // end while(i < m_pattern.size())if(error) {m_error = true;return;}// 模板解析结束之后剩余的常规字符也要算进去if(!tmp.empty()) {patterns.push_back(std::make_pair(0, tmp));tmp.clear();}// for debug // std::cout << "patterns:" << std::endl;// for(auto &v : patterns) {// std::cout << "type = " << v.first << ", value = " << v.second << std::endl;// }// std::cout << "dataformat = " << dateformat << std::endl;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), // m:消息XX(p, LevelFormatItem), // p:日志级别XX(c, LoggerNameFormatItem), // c:日志器名称
// XX(d, DateTimeFormatItem), // d:日期时间XX(r, ElapseFormatItem), // r:累计毫秒数XX(f, FileNameFormatItem), // f:文件名XX(l, LineFormatItem), // l:行号XX(t, ThreadIdFormatItem), // t:编程号XX(F, FiberIdFormatItem), // F:协程号XX(N, ThreadNameFormatItem), // N:线程名称XX(%, PercentSignFormatItem), // %:百分号XX(T, TabFormatItem), // T:制表符XX(n, NewLineFormatItem), // n:换行符
#undef XX};//根据解析得到的模式项创建相应的格式化项对象,并将其添加到m_items中。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;}
}
这篇关于协程库项目—日志模块的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!