文章目录
- 一、整体结构
- 二、LogEvent
- 三、LogLevel
- 四、LogFormatter
- 五、LogAppender
- 六、Logger
- 七、调试
- 7.1调试步骤
- 7.2尝试使用gdb调试
- 八、附录
- 8.1log.h
- 8.2log.cc
- 8.3test.cc
- 8.4Cmakelists.txt
- 8.4Cmakelists.txt
本篇文章主要针对一下sylar高性能服务器项目视频p1-p6的代码分析以及调试,大佬在讲课时基本上都是码代码,很少去详细的讲解代码的细节。像我这种没接触过服务器项目的小白看起来都很有压力,还别说能运行。下面的内容一到六是我自己对当前视频代码的一个简单分析,可能很多地方理解的不对,大家自己取舍。第七部分则是使用gdb进行调试,观察给出的示例是如何运行的,不然照着抄一遍基本看不懂写的什么,如果你写完代码甚至不知道每个模块具体做了什么,推荐可以参考我调试的步骤,没接触过gdb也无妨,只需要知道几个简单命令就行。
最后我在附录中留了前面6节视频完整代码,在保证环境配置正确下可以完美进行调试。
项目视频链接
sylar
一、整体结构
日志模块类似于Log4j,目前的结构如下:
|–LogEvent
|–LogLevel
|–LogFormatter
|–LogAppender
|–StdoutAppender
|–FileLogAppender
|–Logger
- LogEvent:日志的详细描述,包括文件名、线程ID、程序启动时间等一系列的信息
- LogLevel:定义日志的级别
- LogFormatter:定义日志的格式化输出
- LogAppender:控制日志的输出地
- StdoutAppender:将日志输出到控制台
- FileLogAppender:将日志输出保存到文件
- Logger:日志器,可以添加多个LogAppender和LogFormatter对象将其输出到多个地方
这里引用一下博主[令人头疼的编程]画的图,非常直观。
sylar在写代码时大量用到了智能指针,同样引用一下上面博主画的类之间的指针结构图,看代码时参照会轻松很多。
二、LogEvent
下列是目前定义的日志事件所包含的一些内容,可以看到sylar在定义变量时非常规范,常量、变量的范围、有符号或无符号都分得很清楚。
const char* m_file = nullptr; // 文件名
int32_t m_line = 0; // 行号,引入头文件stdint.h
uint32_t m_eplase = 0; // 程序启动到现在的毫秒
uint32_t m_threadId = 0; // 线程ID
uint32_t m_fiberId = 0; // 协程ID
uint64_t m_time; // 时间戳
std::stringstream m_ss; // 内容
为了防止不同编译器中int所占的大小不同,在定义这些变量时基本使用重定义模式,而对于一些不应该为负数的变量,我们应该定义为无符号数,减少程序的漏洞。
sylar在定义日志内容时使用了stringstream而不是string,这是因为对于日志内容的数据可能经常需要进行类型转换,使用stringstream会更加的方便和安全
LogEvent的函数就比较简单,包括一个构造函数以及其它成员变量的取值函数。
// log.h
typedef std::shared_ptr<LogEvent> ptr; // [智能指针]
LogEvent(const char* file, int32_t line, uint32_t eplase
, uint32_t threadId, uint32_t fiberId, uint64_t time);
const char* getFile() const { return m_file;}
int32_t getLine() const { return m_line; }
uint32_t getEplase() const { return m_eplase; }
uint32_t getThreadId() const { return m_threadId; }
uint32_t getFiberId() const { return m_threadId; }
uint64_t getTime() const { return m_time; }
const std::string getcContent() const { return m_ss.str(); }
std::stringstream& getSS() { return m_ss; }
// log.cc
LogEvent::LogEvent(const char* file, int32_t line, uint32_t eplase
, uint32_t threadId, uint32_t fiberId, uint64_t time)
:m_file(file),
m_line(line),
m_eplase(eplase),
m_threadId(threadId),
m_fiberId(fiberId),
m_time(time) {
}
三、LogLevel
定义日志的级别,方便后期对不同的日志进行分类或者过滤接收某些日志。
class LogLevel {
public:
enum Level{
UNKNOW = 0, // 未知 级别
DEBUG = 1, // DEBUG 级别
INFO = 2, // INFO 级别
WARN = 3, // WARN 级别
ERROR = 4, // ERROR 级别
FATAL = 5 // FATAL 级别
};
static const char* ToString(LogLevel::Level level);
};
ToString提供从日志级别到文本的转换,sylar使用了宏函数简化了繁琐的switch/case操作,以前看primer时也了解过宏函数的使用,不过属于看了就扔一边,平时写代码可以说基本没用过,第一次在真实项目中看到比较震撼,以后可以学着多用。后面代码中也有非常多的宏函数使用。
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 "UNKONW";
}
return "UNKONW";
}
四、LogFormatter
FormatItem作为LogFormatter的公共内部类成员,将日志内容格式化。
class LogFormatter {
public:
typedef std::shared_ptr<LogFormatter> ptr;
LogFormatter(const std::string& pattern);
std::string format(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event);
public:
class FormatItem { // [类成员]
public:
typedef std::shared_ptr<FormatItem> ptr;
virtual ~FormatItem() {}
virtual void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) = 0;
};
void init();
private:
std::string m_pattern; // 解析格式
std::vector<FormatItem::ptr> m_items; // 解析内容
};
格式化日志到流
不同类别的format继承于FormatItem,并重写format函数。
class MessageFormatItem : public LogFormatter::FormatItem { // 消息format
public:
MessageFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getcContent();
}
};
class LevelFormatItem : public LogFormatter::FormatItem { // 级别format
public:
LevelFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << LogLevel::ToString(level);
}
};
class LineFormatItem : public LogFormatter::FormatItem {
public:
LineFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getLine();
}
};
class StringFormatItem : public LogFormatter::FormatItem {
public:
StringFormatItem(const std::string& str)
:m_string(str) {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << m_string;
}
private:
std::string m_string;
};
class EplaseFormatItem : public LogFormatter::FormatItem {
public:
EplaseFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getEplase();
}
};
class LoggerNameFormatItem : public LogFormatter::FormatItem {
public:
LoggerNameFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << logger->getName();
}
};
class ThreadIdFormatItem : public LogFormatter::FormatItem {
public:
ThreadIdFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getThreadId();
}
};
class FiberIdFormatItem : public LogFormatter::FormatItem {
public:
FiberIdFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getFiberId();
}
};
class DateTimeFormatItem : public LogFormatter::FormatItem {
public:
DateTimeFormatItem(const std::string& format = "%Y:%m:%d %H:%M:%S")
: m_format(format) {
}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getTime();
}
private:
std::string m_format;
};
init
这个函数应该是前6节视频中最复杂的
- 首先创建一个存储三元组的容器vec,用于
- 第一个for循环
- m_pattern是一个字符串,表示传入的格式模板,比如
%Y:%m:%d %H:%M:%S
,依次循环取m_pattern中的字符,分别判断普通字符、%、{}、空格等 - (A) 代码对应有标记,给出了注释。
- 进入到判断(B),说明当前字符是%,如果后面还有字符并且也是%,则不需要对当前的%作任何处理,进而去判断下一个%
- 进入©,说明正好获取到了一个%,并且后面的字符不是%,那么就可以进行分析,这里定于了几个变量,直接看代码注释。
- 进入(D)是一个while循环,从当前字符的下一个元素n开始,如果下标为n的字符不是字母,或者{},直接退出循环。如果fmt_status为0,表示还没有遇到过{,进入语句,如果当前字符为{,则截取一段字符串,范围为(i + 1, n - i - 1),i是进入(D)之前的左边界下标,n是在while循环里变化的右边界下标,每当遇到一个{,就会把它左边的字符全部放入内容字符串str中,然后把fmt_status赋值1,fmt_begin赋值为n,记录下一个开始的位置。
- 如果fmt_status为1,并且遇到了},同样也是截取一段字符串,赋值给fmt,这时fmt就表示大括号里面的字符,最后把状态置为2,表示处理完了括号里面的数据。
- 整个while循环的目的就是为了解析时间格式,我们传入的模板比如是{%d{%Y-%m-%d …}},经过这段代码,str = d,表示正在解析的是时间,fmt就等于解析后的时间格式
%Y-%m-%d ...
- 进入(E),退出了while循环,如果状态为0,表示要解析后面的字符,那么就要先把之前解析的内容存入vec容器中;如果状态为1,说明传入的格式模板不正确,输出错误提示,并往vec中写入一个错误信息;如果状态为2,这里感觉代码重复了,后面sylar应该会修改。
- m_pattern是一个字符串,表示传入的格式模板,比如
- 进入(F),m_pattern的格式模板已经全部解析完成,定义一个map,第一个参数为相应的日志格式,第二个参数为FormatItem智能指针。里面存放不同类型的FormatItem,比如MessageFormatItem、LevelFormatItem,前面都有相应的定义。然后变量vec,先判断三元组第二个参数是否等于0,如果是说明这一条元组是一个类型字符代号(%d就是时间),存入到m_items中,然后调用相应的format方法。如果不是0,表示相应字符代号后面的内容,则去创建的map里面遍历,找到了就push到m_items,否则push一条错误信息。
void LogFormatter::init() {
std::vector<std::tuple<std::string, std::string,int>> vec; // [tuple] str:内容,format:格式模板, type:0表示格式模板中[]里面的字符代号,比如时间d,消息m,1表示代号跟着的内容
std::string nstr; // 结果字符串
for(size_t i = 0; i < m_pattern.size(); ++ i) {
if(m_pattern[i] != '%') { // (A)如果当前字符不是%,直接加入结果字符串nstr
nstr.append(1,m_pattern[i]); // [append]
continue;
}
if((i + 1) < m_pattern.size()) { // (B)
if(m_pattern[i + 1] == '%') {
nstr.append(1, '%');
continue;
}
}
size_t n = i + 1; // (C) n表示后一个字符的位置
int fmt_status = 0; // 定义个状态初始为0
std::string str; // 内容字符串,也就是传入格式模板的%m
std::string fmt; // 用于寻找括号
size_t fmt_begin = 0; // 记录当前下标的位置
while(n < m_pattern.size()) { // (D)
if(!isalpha(m_pattern[n]) && m_pattern[n] != '{' && m_pattern[n] != '}') { // 判断当前字符是否维空格
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;
}
}
if(fmt_status == 1) {
if(m_pattern[i] == '}') { // 结束解析时间格式
fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1); // fmt是时间格式,比如%Y-%m-%d之类
fmt_status = 2;
break;
}
}
++ n;
}
if(fmt_status == 0) { // (E)
if(!nstr.empty()) {
vec.push_back(std::make_tuple(nstr, std::string(), 0));
nstr.clear();
}
str = m_pattern.substr(i + 1, n - i - 1);
vec.push_back(std::make_tuple(str, fmt, 1));
i = n - 1;
} else if(fmt_status == 1) {
std::cout << "pattern parse error: " << m_pattern << " - " << m_pattern.substr(i) << std::endl;
vec.push_back(std::make_tuple("<parse_error>", fmt, 0));
} else if(fmt_status == 2) {
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;
}
}
if(!nstr.empty()) {
vec.push_back(std::make_tuple(nstr, std::string(), 0));
}
// [function] 引入function (F)
static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)>> s_format_items = {
// {"m",[](const std::string& fmt) { return FormatItem::ptr(new MessageFormatItem(fmt)); } }
#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, EplaseFormatItem), //r:累计毫秒数
XX(c, LoggerNameFormatItem), //c:日志名称
XX(t, ThreadIdFormatItem), //t:线程id
XX(n, LineFormatItem), //n:换行
XX(d, DateTimeFormatItem), //d:时间
XX(l, LineFormatItem), //l:行号
XX(F, FiberIdFormatItem), //F:协程id
#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) + ">>")));
} 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;
}
}
五、LogAppender
LogAppender定义将日志输出的目的地,包含两个子类StdoutAppender、FileLogAppender,分别将日志输出到控制台和文件,后期也可以增加其它的一些输出地。
成员函数包括一个日志级别m_level和输出的格式器m_formatter。
setFormatter用于定义日志输出使用的格式化方法。getFormatter获得格式化方法。
class LogAppender {
public:
typedef std::shared_ptr<LogAppender> ptr;
virtual ~LogAppender() {} // 为了便于该类的派生类调用,定义为[虚类],
virtual void log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) = 0; // [纯虚函数],子类必须重写
void setFormatter(LogFormatter::ptr val) { m_formatter = val; }
LogFormatter::ptr getFormatter() const { return m_formatter; }
protected:
LogLevel::Level m_level = LogLevel::DEBUG; // 级别,为了便于子类访问该变量,设置在保护视图下
LogFormatter::ptr m_formatter; // 定义输出格式
};
输出到控制台
m_formatter时一个vector,里面包含多个不同类型格式化,调用format函数时会依次遍历输出到流。
这里的条件语句应该是根据日志级别进行一个简单的过滤
// log.h
class StdoutAppender : public LogAppender {
public:
typedef std::shared_ptr<StdoutAppender> ptr;
void log(Logger::ptr logger,LogLevel::Level level, LogEvent::ptr event) override;
private:
};
// log.cc
void StdoutAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
if(level >= m_level) {
std::cout << m_formatter->format(logger,level,event); // namespace "std" has no member "cout" 加入iostream
}
}
输出到文件
成员包括文件名m_filename和文件输出流m_filestream。
reopen函数用于打开一个文件,最后返回应该是判断文件打开是否成功,不知道这里用两个!是什么意思
log函数用于输出到文件
// log.h
class FileLogAppender : public LogAppender {
public:
typedef std::shared_ptr<FileLogAppender> ptr;
FileLogAppender(const std::string& filename); // 输出的文件名
void log(Logger::ptr logger,LogLevel::Level level, LogEvent::ptr event) override; // [override]
bool reopen(); // 重新打开文件,成功返回true
private:
std::string m_filename;
std::ofstream m_filestream; // 引入sstream
};
// log.cc
bool FileLogAppender::reopen() {
if(m_filestream) {
m_filestream.close();
}
m_filestream.open(m_filename);
return !!m_filestream; // [?]
}
void FileLogAppender::log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) {
if(level >= m_level) {
m_filestream << m_formatter->format(logger,level,event);
}
}
六、Logger
成员函数包含日志名称m_name、日志级别m_level、日志输出地集合m_appender、格式器m_formatter。
构造函数进行一个简单初始化日志名、日志级别以及默认日志格式
log函数会遍历m_appender里面的日志输出地,调用对应的Appender的log函数进行输出。
addAppender函数会在当前日志item没有格式器时赋予一个默认值,否则直接加入m_appender集合
delAppender函数删除一个指定的日志输出地
// log.h
class Logger : public std::enable_shared_from_this<Logger>{ // <?>为了传递智能指针参数
public:
typedef std::shared_ptr<Logger> ptr;
Logger(const std::string& name = "root");
void log(LogLevel::Level level, LogEvent::ptr event);
// 不同级别的日志输出函数
void debug(LogEvent::ptr event);
void info(LogEvent::ptr event);
void warn(LogEvent::ptr event);
void fatal(LogEvent::ptr event);
void error(LogEvent::ptr event);
void addAppender(LogAppender::ptr appender); // 添加一个appender
void delAppender(LogAppender::ptr appender); // 删除一个appender
LogLevel::Level getLevel() const { return m_level; } // [const放在函数后]
void setLevel(LogLevel::Level val) { m_level = val; } // 设置级别
const std::string& getName() const { return m_name; }
private:
std::string m_name; // 日志名称
LogLevel::Level m_level; // 级别
std::list<LogAppender::ptr> m_appender; // Appender集合,引入list
LogFormatter::ptr m_formatter; // 格式器
};
// log.cc
Logger::Logger(const std::string& name)
:m_name(name),
m_level(LogLevel::DEBUG) {
m_formatter.reset(new LogFormatter("%d [%p] <%f:%l> %m %n")); // 定义一个默认的日志格式
}
void Logger::log(LogLevel::Level level, LogEvent::ptr event) {
if(level >= m_level) {
auto self = shared_from_this(); // <?> 获得logger的智能指针
for(auto &i : m_appender) {
i->log(self,level, event);
}
}
}
void Logger::addAppender(LogAppender::ptr appender) {
if(!appender->getFormatter()) {
appender->setFormatter(m_formatter); // 保证每一个日志都有默认格式
}
m_appender.push_back(appender);
}
void Logger::delAppender(LogAppender::ptr appender) {
for(auto it = m_appender.begin(); it != m_appender.end(); ++ it) {
if(*it == appender) {
m_appender.erase(it);
break;
}
}
}
七、调试
调试需要用到cmake一系列的工具,我是用的阿里云服务器,很多库都有了,如果没有可以参考下列网站去安装。
项目环境配置
我会把到P6为止的代码贴在文章最后,如果环境没问题是可以编译运行的。下面我也简单写一下自己调试的步骤(已安装必须环境)。
7.1调试步骤
-
在最外层sylar文件夹创建
CMakeLists.txt
,我的项目结构如下,红线划掉的是后面使用cmake命令生成的然后往
CMakeLists.txt
写入下列代码,不懂cmake的可以看看这篇文章:https://blog.csdn.net/weixin_43717839/article/details/128032486。cmake_minimum_required(VERSION 2.8) project(sylar) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O0 -g -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function") set(LIB_SRC sylar/log.cc # sylar/util.cc ) add_library(sylar SHARED ${LIB_SRC}) #add_library(sylar_static STATIC ${LIB_SRC}) #SET_TARGET_PROPERTIES(sylar_static PROPERTIES OUTPUT_NAME "sylar") add_executable(test tests/test.cc) add_dependencies(test sylar) target_link_libraries(test sylar) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
-
在tests文件夹创建一个test.cc文件,写入下列代码
#include <iostream> #include "../sylar/log.h" int main(int argc, char** argv) { sylar::Logger::ptr logger(new sylar::Logger); logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender)); sylar::LogEvent::ptr event(new sylar::LogEvent(__FILE__, __LINE__, 0, 1, 2, time(0))); // event->getSS() << "hello sylar log"; logger->log(sylar::LogLevel::DEBUG, event); std::cout << "hello sylar log" << std::endl; return 0; }
-
然后在终端输入命令
cmake . make
-
如果代码有问题,输入make后会报错,根据提示改就行。在能成功编译的情况下,会在bin文件夹生成一个可执行文件test
-
输入
bin/test
就可以运行,结果如下
7.2尝试使用gdb调试
参考视频以及别人的文章,一行一行把代码看了一遍,不过还是很蒙,所以打算调试一下看测试例子是如何一步一步解析的,正好学习一下gdb调试工具。
没有用过gdb调试可以看看这篇文章https://blog.csdn.net/chen1415886044/article/details/105094688?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169666829816800182168185%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=169666829816800182168185&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-105094688-null-null.142v95chatgptT3_1&utm_term=gdb%E8%B0%83%E8%AF%95&spm=1018.2226.3001.4187
测试gdb是否能执行
-
进入文件夹bin,输入下列命令,出现Reading sy…就说明已经加载成功
-
输入命令run(简写r),因为程序没有断点,直接输出结果
开始调试
test.cc文件代码如下,
#include <iostream>
#include "../sylar/log.h"
int main(int argc, char** argv) {
sylar::Logger::ptr logger(new sylar::Logger);
logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender));
sylar::LogEvent::ptr event(new sylar::LogEvent(__FILE__, __LINE__, 0, 1, 2, time(0)));
// event->getSS() << "hello sylar log";
logger->log(sylar::LogLevel::DEBUG, event);
std::cout << "hello sylar log" << std::endl;
return 0;
}
调试一
首先对sylar::Logger::ptr logger(new sylar::Logger);
使用gdb进行调试
-
进入gdb模式,并在第5行打上一个断点,然后启动
-
输入s进入函数内部,输入l查看当前代码,并在132行继续添加一个断点
-
这里是一个日志器logger的构造函数,输入n进行逐语句执行(当前代码已经执行到了129行),直到遇到刚刚在132行设置的断点,并查看logger4个成员变量的值。可以看到,日志名称和级别都是使用的默认值,而日志输入地appender和格式器formatter目前都还没有初始化
-
目前已经到了第二个断点,这句代码是初始化logger的格式器formatter,括号里新建了一个LogFormatter对象,输入s进入LogFormatter的构造函数。m_pattern =
%d [%p] <%f:%l> %m %n
-
语句现在执行到了203行,init函数会将第二个断点处(132行)传入格式模板进行解析,所以在这里204行设置一个断点,输入s进入函数内部
-
在init函数304行会对三元组容器vec(解析模板后的内容)进行遍历打印,为了方便我们在这里也加入一个断点
-
[%](现在每一个循环遍历的元素我都写在序号后)下面进入init函数的第一个for循环,查看每一次循环后三元组vec、字符标记(例如[d],[m]之类的,)str,字符标记后面的内容fmt、以及nstr。一直输入n直到一次循环结束。m_pattern =
%d [%p] <%f:%l> %m %n
,第一次循环解析的值为’%‘,那么跳过第一个if语句,进入第二个if语句,但是由于下一个字符不是’%‘,继续执行解析当前字符’%’[d]进入while循环,不满足里面的条件判断,n++
[ ]遇到空格,触发第一个条件判断,跳出while循环,执行263行代码
if(!nstr.empty())
fmt_status = 0,表示没有遇到{}里面的内容,当前str = ’d’,nstr为空
到这里第一次循环结束,vec里面有一个元组,解析到了格式模板里面的时间字符d
-
[ ]上一个循环结尾i = n - 1 = 1,经历一个i++,i = 2所以当前元素是空格,进入第一个条件判断,把空格字符加入nstr,结束当前for循环
-
[ [ ] i = 3遇到括号[, 继续加入nstr,然后结束当前循环,目前nstr = " ["
-
[ % ] 遇到%,并且下一个不是%,说明下一个是一个字符标记(p,日志级别),进入while循环,和上面第一次for循环类似,就不详细说明,依次遍历p、]然后遇到一个空格跳出while循环。进入263行代码,目前nstr = " [",所以需要把nstr加入到vec中
现在vec就有两条记录,刚加入的nstr第三个参数为0,可以理解为非字符标记,也就是实际内容。
然后继续截取str = p,这是一个字符标记,同样也写入vec中
-
]继续循环,当前字符为],后面的循环依次把"] <"三个字符加入nstr中
-
[ % ]遇到%,下一个字符为f,则说明遇到字符标记,把之前存入nstr的"] <"存入vec中
然后从while出来后(遇到了空格跳出),str = f,将其存入vec
-
后面的for循环解析格式模板我就跳过了,大致就是遇到%就判断是否是字符标记,遇到空格就把之间 的nstr存入vec,然后再提取字符标记str,再存入vec;如果没有遇到%,就直接把当前元素存入nstr,直到下一次遇到%再做处理。结束for循环后,vec容器存储如下
-
[“d 1”]现在进入断点304,也就是根据我们自定义的map,把vec容器的元素解析为不同的类型formatter(例如消息、时间、level等)。对于vec中的第一个元素“d 1",进入条件判断中的else部分
在map中查找是否存储key为”d“的键值对,找到后实例化对应的DateTimeFormatItem,然后把指针存入到m_items中。遍历完vec容器,最后就得到了一个解析完格式模板后的一个指针容器m_items。
调试2
对语句logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender));
进行调试,回顾一下logger的初始化,我们主要是解析传入的默认格式模板,然后把结果存入指针容器m_items。下图就是vec容器的输出,那么m_items里就存放在例如MessageFormatItem等FormatItem
logger的四个参数除了m_appender以外都进行了初始化,下面就是给logger添加一个日志输出地appender。
-
还是在添加appender的语句打一个断点。然后输入s进入函数内部,
传入的参数是一个输出到控制台的appender,因此进入StdoutAppender类,进行初始化(使用默认构造函数)
appender初始化完成后,调用logger的方法添加,并把之前解析出来的m_formatter存入到该appender中
调试3
针对语句sylar::LogEvent::ptr event(new sylar::LogEvent(__FILE__, __LINE__, 0, 1, 2, time(0)));
到此一个logger已经完完全全构建好了
-
在改语句打断点,进入到event的构造函数,传入的参数前两个因为目前还没有,传入占位符,后3个分别是程序启动的毫秒:0、线程ID:1、携程ID:2、内容默认为空。进入构造函数内部
调试4
针对语句logger->log(sylar::LogLevel::DEBUG, event);
-
在目标语句打上断点,进入logger的log函数
-
遍历m_appender容器,我们只有一个,所以进入相应appender的log函数
在log函数里面调用m_formatter的format方法
来到下面函数,这里代码是错误的,需要把return ss.str()放在for循环后,否则只能打印一个时间
-
m_items存放的是之前解析的指向格式模板的指针
第一个解析的是日期d,所以进入DateTimeFormatItem,调用里面的format进行输出,在时间的format方法里面,会获取当前时间戳,然后转换为本地时间,存入到流中
-
其它的也是相应的formatter进行打印,遍历完appender后,往控制台打印了解析的结果如下
八、附录
8.1log.h
#ifndef __SYLAR_LOG_H__
#define __SYLAR_LOG_H__
#include<string>
#include<stdint.h>
#include<memory>
#include<list>
#include<vector>
#include<sstream>
#include<fstream>
namespace sylar{ // 防止和别人的代码冲突
class Logger; // <把Logger放到这里的目的?> 前面的一些类会用到Logger,不加会报未定义错误
// 日志的一些配置
class LogEvent {
public:
typedef std::shared_ptr<LogEvent> ptr; // [智能指针]
LogEvent(const char* file, int32_t line, uint32_t eplase
, uint32_t threadId, uint32_t fiberId, uint64_t time);
const char* getFile() const { return m_file;}
int32_t getLine() const { return m_line; }
uint32_t getEplase() const { return m_eplase; }
uint32_t getThreadId() const { return m_threadId; }
uint32_t getFiberId() const { return m_threadId; }
uint64_t getTime() const { return m_time; }
const std::string getcContent() const { return m_ss.str(); }
std::stringstream& getSS() { return m_ss; }
private:
const char* m_file = nullptr; // 文件名
int32_t m_line = 0; // 行号,引入头文件stdint.h
uint32_t m_eplase = 0; // 程序启动到现在的毫秒
uint32_t m_threadId = 0; // 线程ID
uint32_t m_fiberId = 0; // 协程ID
uint64_t m_time; // 时间戳
std::stringstream m_ss; // 内容
};
// 自定义日志级别
class LogLevel {
public:
enum Level{
UNKNOW = 0, // 未知 级别
DEBUG = 1, // DEBUG 级别
INFO = 2, // INFO 级别
WARN = 3, // WARN 级别
ERROR = 4, // ERROR 级别
FATAL = 5 // FATAL 级别
};
static const char* ToString(LogLevel::Level level);
};
// 日志格式
class LogFormatter {
public:
typedef std::shared_ptr<LogFormatter> ptr;
LogFormatter(const std::string& pattern);
std::string format(std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event);
public:
class FormatItem { // [类中类]
public:
typedef std::shared_ptr<FormatItem> ptr;
virtual ~FormatItem() {}
virtual void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) = 0;
};
void init();
private:
std::string m_pattern; // 解析格式
std::vector<FormatItem::ptr> m_items; // 解析内容
};
// 日志输出地
class LogAppender {
public:
typedef std::shared_ptr<LogAppender> ptr;
virtual ~LogAppender() {} // 为了便于该类的派生类调用,定义为[虚类],
virtual void log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) = 0; // [纯虚函数],子类必须重写
void setFormatter(LogFormatter::ptr val) { m_formatter = val; }
LogFormatter::ptr getFormatter() const { return m_formatter; }
protected:
LogLevel::Level m_level = LogLevel::DEBUG; // 级别,为了便于子类访问该变量,设置在保护视图下
LogFormatter::ptr m_formatter; // 定义输出格式
};
// 日志输出器
class Logger : public std::enable_shared_from_this<Logger>{ // [?]
public:
typedef std::shared_ptr<Logger> ptr;
Logger(const std::string& name = "root");
void log(LogLevel::Level level, LogEvent::ptr event);
// 不同级别的日志输出函数
void debug(LogEvent::ptr event);
void info(LogEvent::ptr event);
void warn(LogEvent::ptr event);
void fatal(LogEvent::ptr event);
void error(LogEvent::ptr event);
void addAppender(LogAppender::ptr appender); // 添加一个appender
void delAppender(LogAppender::ptr appender); // 删除一个appender
LogLevel::Level getLevel() const { return m_level; } // [const放在函数后]
void setLevel(LogLevel::Level val) { m_level = val; } // 设置级别
const std::string& getName() const { return m_name; }
private:
std::string m_name; // 日志名称
LogLevel::Level m_level; // 级别
std::list<LogAppender::ptr> m_appender; // Appender集合,引入list
LogFormatter::ptr m_formatter;
};
// 输出方法分类
// 输出到控制台
class StdoutAppender : public LogAppender {
public:
typedef std::shared_ptr<StdoutAppender> ptr;
void log(Logger::ptr logger,LogLevel::Level level, LogEvent::ptr event) override;
private:
};
// 输出到文件
class FileLogAppender : public LogAppender {
public:
typedef std::shared_ptr<FileLogAppender> ptr;
FileLogAppender(const std::string& filename); // 输出的文件名
void log(Logger::ptr logger,LogLevel::Level level, LogEvent::ptr event) override; // [override]
bool reopen(); // 重新打开文件,成功返回true
private:
std::string m_filename;
std::ofstream m_filestream; // stringstream要报错,引入sstream
};
}
#endif
8.2log.cc
#include"log.h"
#include<iostream>
#include<map>
#include<functional>
#include<time.h>
#include<string.h>
namespace sylar {
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 "UNKONW";
}
return "UNKONW";
}
class MessageFormatItem : public LogFormatter::FormatItem { // [继承类中类]
public:
MessageFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getcContent();
}
};
class LevelFormatItem : public LogFormatter::FormatItem {
public:
LevelFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << LogLevel::ToString(level);
}
};
class LineFormatItem : public LogFormatter::FormatItem {
public:
LineFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getLine();
}
};
class StringFormatItem : public LogFormatter::FormatItem {
public:
StringFormatItem(const std::string& str)
:m_string(str) {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << m_string;
}
private:
std::string m_string;
};
class EplaseFormatItem : public LogFormatter::FormatItem {
public:
EplaseFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getEplase();
}
};
class LoggerNameFormatItem : public LogFormatter::FormatItem {
public:
LoggerNameFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << logger->getName();
}
};
class ThreadIdFormatItem : public LogFormatter::FormatItem {
public:
ThreadIdFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getThreadId();
}
};
class FiberIdFormatItem : public LogFormatter::FormatItem {
public:
FiberIdFormatItem(const std::string& fmt = "") {}
void format(std::ostream& os, std::shared_ptr<Logger> logger,LogLevel::Level level,LogEvent::ptr event) override {
os << event->getFiberId();
}
};
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 {
// os << event->getTime();
struct tm tm;
time_t time = event->getTime(); // 当前时间戳 引入time.h
localtime_r(&time, &tm); // 将时间戳转换为本地时间,并将结果存放在tm中
char buf[64];
strftime(buf, sizeof(buf), m_format.c_str(), &tm);
os << buf;
}
private:
std::string m_format;
};
LogEvent::LogEvent(const char* file, int32_t line, uint32_t eplase
, uint32_t threadId, uint32_t fiberId, uint64_t time)
:m_file(file),
m_line(line),
m_eplase(eplase),
m_threadId(threadId),
m_fiberId(fiberId),
m_time(time) {
}
Logger::Logger(const std::string& name)
:m_name(name),
m_level(LogLevel::DEBUG) {
m_formatter.reset(new LogFormatter("%d [%p] <%f:%l> %m %n"));
}
void Logger::addAppender(LogAppender::ptr appender) {
if(!appender->getFormatter()) {
appender->setFormatter(m_formatter); // 保证每一个日志都有默认格式
}
m_appender.push_back(appender);
}
void Logger::delAppender(LogAppender::ptr appender) {
for(auto it = m_appender.begin(); it != m_appender.end(); ++ it) {
if(*it == appender) {
m_appender.erase(it);
break;
}
}
}
void Logger::log(LogLevel::Level level, LogEvent::ptr event) {
if(level >= m_level) {
auto self = shared_from_this(); // [?]
for(auto &i : m_appender) {
i->log(self,level, event);
}
}
}
void Logger::debug(LogEvent::ptr event) {
log(LogLevel::DEBUG, event);
}
void Logger::info(LogEvent::ptr event) {
log(LogLevel::INFO, event);
}
void Logger::warn(LogEvent::ptr event) {
log(LogLevel::WARN, event);
}
void Logger::fatal(LogEvent::ptr event) {
log(LogLevel::ERROR, event);
}
void Logger::error(LogEvent::ptr event) {
log(LogLevel::FATAL, event);
}
FileLogAppender::FileLogAppender(const std::string& filename)
: m_filename(filename) {
}
void FileLogAppender::log(std::shared_ptr<Logger> logger,LogLevel::Level level, LogEvent::ptr event) {
if(level >= m_level) {
m_filestream << m_formatter->format(logger,level,event);
}
}
bool FileLogAppender::reopen() {
if(m_filestream) {
m_filestream.close();
}
m_filestream.open(m_filename);
return !!m_filestream; // [?]
}
void StdoutAppender::log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) {
if(level >= m_level) {
std::cout << m_formatter->format(logger,level,event) << std::endl; // namespace "std" has no member "cout" 加入iostream
}
}
LogFormatter::LogFormatter(const std::string& pattern)
: m_pattern(pattern) {
init();
}
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();
}
// 日志格式定义
void LogFormatter::init() {
std::vector<std::tuple<std::string, std::string,int>> vec; // [tuple] str,format, type
std::string nstr; //当前str
for(size_t i = 0; i < m_pattern.size(); ++ i) {
if(m_pattern[i] != '%') {
nstr.append(1,m_pattern[i]); // [append]
continue;
}
if((i + 1) < m_pattern.size()) {
if(m_pattern[i + 1] == '%') {
nstr.append(1, '%');
continue;
}
}
size_t n = i + 1;
int fmt_status = 0;
std::string str;
std::string fmt;
size_t fmt_begin = 0; //
while(n < m_pattern.size()) {
if(!isalpha(m_pattern[n]) && m_pattern[n] != '{' && m_pattern[n] != '}') { // 判断当前字符是否维空格
break;
}
if(fmt_status == 0) {
if(m_pattern[n] == '{') {
str = m_pattern.substr(i + 1, n - i - 1);
fmt_status = 1; // 解析格式
fmt_begin = n;
++ n;
continue;
}
}
if(fmt_status == 1) {
if(m_pattern[i] == '}') {
fmt = m_pattern.substr(fmt_begin + 1, n - fmt_begin - 1);
fmt_status = 2;
break;
}
}
++ n;
}
if(fmt_status == 0) {
if(!nstr.empty()) {
vec.push_back(std::make_tuple(nstr, std::string(), 0));
nstr.clear();
}
str = m_pattern.substr(i + 1, n - i - 1);
vec.push_back(std::make_tuple(str, fmt, 1));
i = n - 1;
} else if(fmt_status == 1) {
std::cout << "pattern parse error: " << m_pattern << " - " << m_pattern.substr(i) << std::endl;
vec.push_back(std::make_tuple("<parse_error>", fmt, 0));
} else if(fmt_status == 2) {
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;
}
}
if(!nstr.empty()) {
vec.push_back(std::make_tuple(nstr, std::string(), 0));
}
// [function] 引入function
static std::map<std::string, std::function<FormatItem::ptr(const std::string& str)>> s_format_items = {
// {"m",[](const std::string& fmt) { return FormatItem::ptr(new MessageFormatItem(fmt)); } }
#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, EplaseFormatItem), //r:累计毫秒数
XX(c, LoggerNameFormatItem), //c:日志名称
XX(t, ThreadIdFormatItem), //t:线程id
XX(n, LineFormatItem), //n:换行
XX(d, DateTimeFormatItem), //d:时间
XX(l, LineFormatItem), //l:行号
XX(f, FiberIdFormatItem), //F:协程id
#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) + ">>")));
} 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;
}
}
}
8.3test.cc
#include <iostream>
#include "../sylar/log.h"
int main(int argc, char** argv) {
sylar::Logger::ptr logger(new sylar::Logger);
logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender));
sylar::LogEvent::ptr event(new sylar::LogEvent(__FILE__, __LINE__, 0, 1, 2, time(0)));
// event->getSS() << "hello sylar log";
logger->log(sylar::LogLevel::DEBUG, event);
// std::cout << "hello sylar log" << std::endl;
return 0;
}
8.4Cmakelists.txt
cmake_minimum_required(VERSION 2.8)
project(sylar)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O0 -g -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function")
set(LIB_SRC
sylar/log.cc
# sylar/util.cc
)
add_library(sylar SHARED ${LIB_SRC})
#add_library(sylar_static STATIC ${LIB_SRC})
#SET_TARGET_PROPERTIES(sylar_static PROPERTIES OUTPUT_NAME "sylar")
add_executable(test tests/test.cc)
add_dependencies(test sylar)
target_link_libraries(test sylar)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
d::get<0>(i) << “) - (” << std::get<1>(i) << “) - (” << std::get<2>(i) << ‘)’ << std::endl;
}
}
}
### 8.3test.cc
```c
#include <iostream>
#include "../sylar/log.h"
int main(int argc, char** argv) {
sylar::Logger::ptr logger(new sylar::Logger);
logger->addAppender(sylar::LogAppender::ptr(new sylar::StdoutAppender));
sylar::LogEvent::ptr event(new sylar::LogEvent(__FILE__, __LINE__, 0, 1, 2, time(0)));
// event->getSS() << "hello sylar log";
logger->log(sylar::LogLevel::DEBUG, event);
// std::cout << "hello sylar log" << std::endl;
return 0;
}
8.4Cmakelists.txt
cmake_minimum_required(VERSION 2.8)
project(sylar)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O0 -g -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function")
set(LIB_SRC
sylar/log.cc
# sylar/util.cc
)
add_library(sylar SHARED ${LIB_SRC})
#add_library(sylar_static STATIC ${LIB_SRC})
#SET_TARGET_PROPERTIES(sylar_static PROPERTIES OUTPUT_NAME "sylar")
add_executable(test tests/test.cc)
add_dependencies(test sylar)
target_link_libraries(test sylar)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)