Sylar服务器框架 日志模块 源码分析
文章目录
日志库模块整体设计
文章目录
先上图
关键的类
sylar日志库 一共6个日志级别 UNKNOW,DEBUG,INFO,WARN,ERROR,FATAL
// 日志级别
class LogLevel;
// 日志格式化
class LogFormatter;
// 日志输出目标
class LogAppender;
// 输出到控制台的Appender
class StdoutLogAppender : public LogAppender;
// 输出到文件的Appender
class FileLogAppender : public LogAppender;
// 日志器
class Logger;
// 日志事件
class LogEvent;
// 日志事件包装器
class LogEventWrap;
// 日志器管理类
class LogManager;
上述类详细设计:
- LogFormatter:日志格式器,可用于格式化日志事件,通过format方法用于将日志事件格式化成字符串;
- LogAppender:日志输出目标,用于将日志事件输出到对应的输出地,该类包含LogFormatter格式化方法和log方法,可通过继承该类实现到不同的输出地输出;
- Logger:日志器,负责日志输出,提供log方法,若该日志需要输出则调用LogAppender将日志输出,否则抛弃;
- LogEvent:日志时间,用于记录日志信息,比如该日志的级别,文件名/行号,日志消息,线程/协程号,所属日志器名称等。;
- LogEventWrap:日志事件包装器,将日志事件与日志器包装在一起,通过宏定义简化日志模块的使用,当期析构时候,则调用日志器的log方法进行输出;
- LogManager:日志器管理类,单例模式,用于统一管理所有的日志器,提供日志器的创建与获取方法,内部维护一个名称到日志器的map,当获取的日志器存在时,直接返回对应的日志器指针,否则创建对应的日志器并返回。
日志库整体流程和重要函数说明(最终是由LogAppender的LogFormatter负责相应调用完成最终日志输出)
1、初始化Logger,LogFormatter,LogAppender;
2、通过宏定义生成LogEventWrap对象;
3、LogEventWrap对象析构时,调用log方法输出日志。
总结:通过Loggermanager管理Logger写日志:通过LogEventWrap析构触发不同logger上的LogEvent不同的logger往不同的LoggerAppender上写日志。
红线为 formatter类 在不同类中的 转移流程
sylar::Logger::ptr logger(new sylar::Logger);
// 控制台输出
logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutLogAppender));
// 文件输出,输出到"./log.txt"
sylar::FileLogAppender::ptr file_appender(new sylar::FileLogAppender("./log.txt"));
// 标准化格式(自定义
sylar::LogFormatter::ptr fmt(new sylar::LogFormatter("%d%T%p%T%m%n"));
// 设置输出到文件的标准化格式
file_appender->setFormatter(fmt);
// 设置输出到文件的日志级别
file_appender->setLevel(sylar::LogLevel::ERROR);
// 添加文件输出地
logger->addAppender(file_appender);
// 设置日志事件
sylar::LogEvent::ptr event2(new sylar::LogEvent(logger,sylar::LogLevel::ERROR1,__FILE__, __LINE__, 0, 1, 2, time(0),"root"));
// 绑定日志事件
sylar::LogEventWrap wrap(event2);
// 输出hello
wrap.getEvent()->getSS() << "hello";
// 输出
logger->log(sylar::LogLevel::ERROR, wrap.getEvent());
wrap.getEvent()->format("test macro fmt error %c", 'c');
重要代码逐行分析
LogFormatter::init —— 格式解析函数(100行左右)
详情请见https://github.com/zhongluqiang/sylar-from-scratch/blob/main/sylar/log.cpp
/**
* @brief 构造函数
* @param[in] pattern 格式模板
* @details
* %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"
*/
结果
2022-02-17 18:34:34 1 test 2 [DEBUG] [root] /home/knight/framework/tests/test.cc:7
-
%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n
-
第一次 遍历结果是 str = “d” ——fmt = “%Y-%m-%d %H:%M:%S”
-
“T” “t”
-
nstr是用来解析 [ ]等类似字符 —— 对应 StringFormatItem,
-
分别加入后,由logger->log函数负责一个个按顺序调用,最终实现日志打印
大致流程
从第一个字母开始遍历
第i个是否% | ||
---|---|---|
是 | 第i+1是否字母 | |
否,加入nstr | ||
是 | 第i+1个是否是 { | |
否,( 且不为{ )退出循环 | ||
是,进入解析状态,直到遇到 } | ||
否,退出循环 |
// 源代码第12行 ~ 19行 无用代码
/*if((i + 1) < m_pattern.size()) {
if(m_pattern[i + 1] == '%') { // 两个%%的情况,基本不会遇到可忽略
nstr.append(1, '%'); // 字符串末尾添加一个%
continue;
}
}*/
// %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() {
//str, 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) { // 遍历m_pattern字符串
if(m_pattern[i] != '%') {
nstr.append(1, m_pattern[i]); // 字符串末尾添加一个m_pattern[i]
continue;
}
size_t n = i + 1; // 从 i + 1开始遍历
int fmt_status = 0; // 是否处于解析状态
size_t fmt_begin = 0; // 解析起始位置
std::string str;
std::string fmt;
while(n < m_pattern.size()) { // 解析{ }
if(!fmt_status && (!isalpha(m_pattern[n]) && m_pattern[n] != '{'
&& 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); // 取一个字符
//std::cout << "*" << str << std::endl;
fmt_status = 1; //解析格式
fmt_begin = n;
++n;
continue;
}
} else if(fmt_status == 1) {
if(m_pattern[n] == '}') {
fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1); //截取{ }
//std::cout << "#" << fmt << std::endl;
fmt_status = 0;
++n;
break;
}
}
++n;
if(n == m_pattern.size()) {
if(str.empty()) {
str = m_pattern.substr(i + 1);
}
}
}
if(fmt_status == 0) {
if(!nstr.empty()) { // 解析[ ]
vec.push_back(std::make_tuple(nstr, std::string(), 0));
nstr.clear();
}
vec.push_back(std::make_tuple(str, fmt, 1));
i = n - 1; // i 从} 开始从新遍历
} else if(fmt_status == 1) {
std::cout << "pattern parse 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));
}
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(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) {
m_items.push_back(FormatItem::ptr(new StringFormatItem(std::get<0>(i)))); // 放入 []
} else {
auto it = s_format_items.find(std::get<0>(i));
if(it == s_format_items.end()) {
m_items.push_back(FormatItem::ptr(new StringFormatItem("<<error_format %" + std::get<0>(i) + ">>")));
m_error = true;
std::cout << "error" << std::endl;
} else {
m_items.push_back(it->second(std::get<1>(i)));
}
}
// std::cout << "(" << std::get<0>(i) << ") - (" << std::get<1>(i) << ") - (" << std::get<2>(i) << ")" << std::endl;
}
// std::cout << m_items.size() << std::endl;
}