以下内容是sylar高性能服务器视频的简单记录,如果你在调试代码时遇到了困难那么下面内容会有帮助。
文章目录
- p15:配置变更事件
- 一、函数
- 二、结果展示
- p16:日志系统的整合1
- 一、函数
- 二、小结
- p17-18:日志系统的整合2、3
- 一、函数
- 二、结果展示
- P19:日志系统的整合4
- 一、打印格式冗余问题
- 二、由于配置文件的存在,后期改变了formatter不会生效
- 三、文件名不应该输出全部路径
- p20:日志系统的整合5
p15:配置变更事件
配置系统部分的最后一节,本节主要内容满足下列功能:
- 当服务器在运行过程中,如果配置的内容进行了更改,那么程序会返回更改前的值和新值,对更改配置行为进行监听
一、函数
-
使用
typedef
定义一个回调函数,std::function是可调用对象的wrapper,它可以包装函数、lambda表达式、bind表达式、函数对象、成员函数指针、成员变量指针,定义在头文件<functional>
。typedef std::function<void (const T& old_value, const T& new_value)> on_change_cb;
-
使用
map
存放监听函数,这里使用map
作为存储结构是因为functional
没有比较函数,如果要删除一个function,或者在容器里面判断是否是一个相等的函数是无法做到的,key值就可以辅助我们删除,要求唯一可以使用hashstd::map<uint64_t,on_change_cb> m_cbs;
-
其它监听器功能函数
// 添加监听函数 void addListener(uint64_t key,on_change_cb cb) { m_cbs[key] = cb; } // 删除监听函数 void delListener(uint64_t key) { m_cbs.erase(key); } // 获取监听函数 on_change_cb getListener(uint64_t key) { auto it = m_cbs.find(key); return it == m_cbs.end() ? nullptr : it->second; } // 清空所以监听器 void clearListener() { m_cbs.clear(); }
-
更改
ConfigVar
的赋值函数void setValue(const T& v) { // 注意之前我们定义的自定义类型Person没有重载==,需要添加 if(v == m_val) { // 如果新值和原值一样,说明无变化直接返回 return; } for(auto& i : m_cbs) { // 有变化则通过map中key找到对应配置更改 i.second(m_val, v); // 传入定义的监听器函数,具体做什么由该监听器决定 } m_val = v; }
二、结果展示
-
定义监听器
// 监控配置的使用 g_person->addListener(10,[](const Person& old_vale, const Person& new_vale){ SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "oldVale: " << old_vale.toString() << " newValue: " << new_vale.toString(); });
-
结果:可以看到当我们通过配置文件改变参数后,程序进行了提示
p16:日志系统的整合1
本节将开启日志系统的整合,将会回顾修改很多以前的代码,建议大家可以看看之前的笔记,反正我是忘得差不多了。本节主要内容如下:
- 当用户定义一个logger而日志管理容器中没有时,不用赋予其m_root,而是创建一个新的logger,用m_root的变量去初始化logger的空变量。
一、函数
-
日志格式
logs: - name: root level: (debug,info,warn,error,fatal) formatter: '%d%T%m%n' appenders: - type: (FileLogAppender, StdoutLogAppender) level: (debug....) file: /logs/xxx.log
-
新增查找logger的宏函数
#define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger()
-
提出本节需要解决的问题,当程序运行时我们定义的了一个log,但是
yml
文件中没有名为system
,那么以前的程序就会把root
赋予给我们。因此我们希望这种情况下程序应该创建一个新的日志system
返回给我们,这样我们后期想要往system
写东西时就可以做到。sylar::Logger g_log = sylar::LoggerMgr::GetInstance()->getLogger("system"); SYLAR_LOG_INFO(g_logger) << "xxx log";
-
修改
Logger
日志输出器,新增成员变量;让Logger
成为LoggerManager
的友元函数Logger::ptr m_root; friend class LoggerManager;
-
修改
LoggerManager
的getLogger
方法,这里我们就已经创建了一个用户指定名称的log
,并且存放在m_loggers
中(<string,looger>)。Logger::ptr LoggerManager::getLogger(const std::string& name) { auto it = m_loggers.find(name); if(it != m_loggers.end()) { return it->second; } // 不存在则创建 Logger::ptr logger(new Logger(name)); // 创建的logger什么都没有,则根据root赋值 logger->m_root = m_root; m_loggers[name] = logger; return logger; }
-
修改
Logger
的log
方法,新增功能:如果当前日志的appender
为空,则使用m_root
的appender
打印,否则正常打印。void Logger::log(LogLevel::Level level, LogEvent::ptr event) { if(level >= m_level) { auto self = shared_from_this(); if(!m_appender.empty()) { // 判断当前日志的appender是否为空 for(auto &i : m_appender) { i->log(self,level, event); } } else if(m_root){ m_root->log(level,event); } } }
-
LoggerManager
的构造函数,新增init()
方法,本节还未实现 -
新增结构体
LogDefine
struct LogDefine{ std::string name; LogLevel::Level level = LogLevel::UNKNOW; std::string formatter; std::vector<LogAppenderDefine> appenders; bool operator==(const LogDefine& oth) const{ return name == oth.name && level == oth.level && formatter == oth.formatter && appenders == oth.appenders; } bool operator<(const LogDefine& oth) const { return name < oth.name; } };
-
新增结构体
LogAppenderDefine
struct LogAppenderDefine { int type = 0; // 1 File. 2 Stdout LogLevel::Level level = LogLevel::UNKNOW; std::string formatter; std::string file; bool operator==(const LogAppenderDefine& oth) const { return type ==oth.type && level == oth.level && formatter == oth.formatter && file == oth.file; } };
二、小结
以上就是本节的内容,一些函数具体的实现过程还未实现,我自己在写的时候不太明白一些函数的实际用途,看后续章节的实例应该会好点。
p17-18:日志系统的整合2、3
上一节声明了日志系统整合需要的基础类,本节主要在此基础上设计偏特化实现配置文件与String
之间的转化,以及绑定监听事件。重点注意sylar在p17暂停录制去改代码,然后改完也没讲,如果你跟着视频敲完出现segmentation fault (core dumped)的问题就是因为某些函数没有修改。目前写到这里我自己感觉理解得很乱,所以下面只是一些简单的记录,等以后二刷再来深究。不过我会把调试遇到的问题都罗列出来,可以减少大家的调试时间,比如本节sylar改了代码不说,debug也找不到的位置(不过了解了gdb中使用core文件找bug,可以去百度一下,加强对gdb的理解也是非常重要),浪费我整整3个小时,这两节视频还重新看了一遍。
一、函数
注意我不会把所有更改都贴出来,只是展示几个核心函数。
LogInit
该函数的作用主要是当读取用户的yml
配置文件时如果发生了变化进行相应的处理。
struct LogIniter {
LogIniter() {
// 给日志绑定一个事件
g_log_defines->addListener(1213,[](const std::set<LogDefine>& old_value,
const std::set<LogDefine>& new_value){
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "on_logger_conf_changed";
for(auto& i : new_value) {
auto it = old_value.find(i);
sylar::Logger::ptr logger;
if(it == old_value.end()) {
//新增logger
logger = SYLAR_LOG_NAME(i.name);
} else {
if(!(i == *it)) {
//修改的logger
logger = SYLAR_LOG_NAME(i.name);
} else {
continue;
}
}
logger->setLevel(i.level);
if(!i.formatter.empty()) {
logger->setFormatter(i.formatter);
}
logger->clrAppender();
for(auto& a : i.appenders) {
sylar::LogAppender::ptr ap;
if(a.type == 1) {
ap.reset(new FileLogAppender(a.file));
} else if(a.type == 2) {
ap.reset(new StdoutAppender);
}
ap->setLevel(a.level);
if(!a.formatter.empty()) {
LogFormatter::ptr fmt(new LogFormatter(a.formatter));
if(!fmt->isError()) {
ap->setFormatter(fmt);
} else {
std::cout << "log.name=" << i.name << " appender type=" << a.type
<< " formatter=" << a.formatter << " is invalid" << std::endl;
}
}
logger->addAppender(ap);
}
}
for(auto& i : old_value) {
auto it = new_value.find(i);
if(it == new_value.end()) {
//删除logger
auto logger = SYLAR_LOG_NAME(i.name);
logger->setLevel((LogLevel::Level)0);
logger->clrAppender();
}
}
});
}
};
偏特化
和在P15里写的自定义配置类型很相似。
template<>
class LexicalCast<std::string, LogDefine> {
public:
LogDefine operator()(const std::string& v) {
YAML::Node n = YAML::Load(v);
LogDefine ld;
if(!n["name"].IsDefined()) {
std::cout << "log config error: name is null, " << n
<< std::endl;
throw std::logic_error("log config name is null");
}
ld.name = n["name"].as<std::string>();
ld.level = LogLevel::FromString(n["level"].IsDefined() ? n["level"].as<std::string>() : "");
if(n["formatter"].IsDefined()) {
ld.formatter = n["formatter"].as<std::string>();
}
if(n["appenders"].IsDefined()) {
for(size_t x = 0; x < n["appenders"].size(); ++x) {
auto a = n["appenders"][x];
if(!a["type"].IsDefined()) {
std::cout << "log config error: appender type is null, " << a
<< std::endl;
continue;
}
std::string type = a["type"].as<std::string>();
LogAppenderDefine lad;
if(type == "FileLogAppender") {
lad.type = 1;
if(!a["file"].IsDefined()) {
std::cout << "log config error: fileappender file is null, " << a
<< std::endl;
continue;
}
lad.file = a["file"].as<std::string>();
if(a["formatter"].IsDefined()) {
lad.formatter = a["formatter"].as<std::string>();
}
} else if(type == "StdoutLogAppender") {
lad.type = 2;
if(a["formatter"].IsDefined()) {
lad.formatter = a["formatter"].as<std::string>();
}
} else {
std::cout << "log config error: appender type is invalid, " << a
<< std::endl;
continue;
}
ld.appenders.push_back(lad);
}
}
return ld;
}
};
template<>
class LexicalCast<LogDefine, std::string> {
public:
std::string operator()(const LogDefine& i) {
YAML::Node n;
n["name"] = i.name;
if(i.level != LogLevel::UNKNOW) {
n["level"] = LogLevel::ToString(i.level);
}
if(i.formatter.empty()) {
n["formatter"] = i.formatter;
}
for(auto& a : i.appenders) {
YAML::Node na;
if(a.type == 1) {
na["type"] = "FileLogAppender";
na["file"] = a.file;
} else if(a.type == 2) {
na["type"] = "StdoutLogAppender";
}
if(a.level != LogLevel::UNKNOW) {
na["level"] = LogLevel::ToString(a.level);
}
if(!a.formatter.empty()) {
na["formatter"] = a.formatter;
}
n["appenders"].push_back(na);
}
std::stringstream ss;
ss << n;
return ss.str();
}
};
Config修改
在配置管理类Config
中之前我们定义了一个静态变量s_datas
,而同时在该类中还有一个Lookup
,有一个问题就是静态成员它们的初始化顺序是随机的,而在Lookup
中使用了s_datas
,如果此时s_datas
并没有初始化,运行程序就会报错。所以把该静态成员放在静态函数里,Lookup
中通过该函数来获取s_datas
,就一定能提前初始化s_datas
。
下面是gdb
调试的截图,就这个报错信息死活都找不到。
```
static typename ConfigVar<T>::ptr Lookup(const std::string& name,
const T& default_value, const std::string& description = "")
```
static ConfigVarMap& GetDatas() {
static ConfigVarMap s_datas;
return s_datas;
}
// config.cc文件有些地方也需要更改,使用方式:`GetDatas().find(name);`
ConfigvarBase::ptr Config::LookupBase(const std::string& name) {
auto it = GetDatas().find(name);
return it == GetDatas().end() ? nullptr : it->second;
}
二、结果展示
测试文件
void test_log() {
static sylar::Logger::ptr system_log = SYLAR_LOG_NAME("system");
SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
YAML::Node root = YAML::LoadFile("/root/Web-learning/sylar/bin/conf/log.yml");
sylar::Config::LoadFromYaml(root);
std::cout << "======================" << std::endl;
std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
}
int main(int argc, char** argv) {
test_log();
return 0;
}
测试log
logs:
- name: root
level: INFO
formatter: '%d%T%m%n'
appenders:
- type: FileLogAppender
file: /root/Web-learning/sylar/log.txt
- type: StdoutLogAppender
- name: system
level: DEBUG
formatter: '%d%T%m%n'
appenders:
- type: FileLogAppender
file: /root/Web-learning/sylar/system.txt
- type: StdoutLogAppender
结果
可以看到我们以yml
的方式从文件中加载了配置项并对默认的配置进行了修改
P19:日志系统的整合4
本节主要内容主要是解决下列3个问题
- 打印格式冗余问题
- 由于配置文件的存在,后期改变了formatter不会生效
- 文件名不应该输出全部路径
一、打印格式冗余问题
如下图,当我们读取配置文件log.yml
的信息并打印到控制台,发现及时我们没有在配置中给出appender
的打印格式,但是由于继承的关系拥有了父类的格式,所以我们想做的是如果appender
没有格式则不打印。
首先在类LogAppender
中新增一个成员变量m_hasFormatter
,并把该类作为logger
的友元类:friend class Logger;
。
bool m_hasFormatter = false; // 是否有自己的日志格式器
之后我们在设置appender
的格式时,就需要根据是否有格式更新m_hasFormatter
的值
void LogAppender::setFormatter(LogFormatter::ptr val) {
m_formatter = val;
if(m_formatter) {
m_hasFormatter = true;
} else {
m_hasFormatter = false;
}
}
最后来到Appender
的toYamlString
函数,就可以根据m_hasFormatter
的值来决定是否给当前appender
设置打印格式
std::string FileLogAppender::toYamlString() {
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();
}
然后看看效果,现在如果不在log.yml
中给appender
设置打印格式,就不会直接基础父类的打印格式
二、由于配置文件的存在,后期改变了formatter不会生效
下列测试代码我在最后更改了log
的打印格式,但是控制台却还是保留配置文件中的打印格式。
void test_log() {
static sylar::Logger::ptr system_log = SYLAR_LOG_NAME("system");
SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
YAML::Node root = YAML::LoadFile("/root/Web-learning/sylar/bin/conf/log.yml");
sylar::Config::LoadFromYaml(root);
std::cout << "======================" << std::endl;
std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
std::cout << "======================" << std::endl;
std::cout << root << std::endl;
SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
//
system_log->setFormatter("%d - %m%n");
SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
}
与之前类似,更改Logger
的setFormatter
方法,防止它们直接基础父类的打印格式从而忽略掉后期格式的修改
void Logger::setFormatter(LogFormatter::ptr val) {
m_formatter = val;
// 防止所有的appender在任何情况下都直接继承log的格式
for(auto& i : m_appender) { // 遍历该log的appender
if(!i->m_hasFormatter) { // 如果当前appender没有自己的格式,才则继承log的格式
i->m_formatter = m_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;
}
setFormatter(new_val);
}
测试结果:
三、文件名不应该输出全部路径
该问题主要是对CmakeList.txt
的修改,首先在cmake
文件夹下引入了一个文件utils.cmake
function(force_redefine_file_macro_for_sources targetname)
get_target_property(source_files "${targetname}" SOURCES)
foreach(sourcefile ${source_files})
# Get source file's current list of compile definitions.
get_property(defs SOURCE "${sourcefile}"
PROPERTY COMPILE_DEFINITIONS)
# Get the relative path of the source file in project directory
get_filename_component(filepath "${sourcefile}" ABSOLUTE)
string(REPLACE ${PROJECT_SOURCE_DIR}/ "" relpath ${filepath})
list(APPEND defs "__FILE__=\"${relpath}\"")
# Set the updated compile definitions on the source file.
set_property(
SOURCE "${sourcefile}"
PROPERTY COMPILE_DEFINITIONS ${defs}
)
endforeach()
endfunction()
function(ragelmaker src_rl outputlist outputdir)
#Create a custom build step that will call ragel on the provided src_rl file.
#The output .cpp file will be appended to the variable name passed in outputlist.
get_filename_component(src_file ${src_rl} NAME_WE)
set(rl_out ${outputdir}/${src_file}.rl.cc)
#adding to the list inside a function takes special care, we cannot use list(APPEND...)
#because the results are local scope only
set(${outputlist} ${${outputlist}} ${rl_out} PARENT_SCOPE)
#Warning: The " -S -M -l -C -T0 --error-format=msvc" are added to match existing window invocation
#we might want something different for mac and linux
add_custom_command(
OUTPUT ${rl_out}
COMMAND cd ${outputdir}
COMMAND ragel ${CMAKE_CURRENT_SOURCE_DIR}/${src_rl} -o ${rl_out} -l -C -G2 --error-format=msvc
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${src_rl}
)
set_source_files_properties(${rl_out} PROPERTIES GENERATED TRUE)
endfunction(ragelmaker)
function(protobufmaker src_proto outputlist outputdir)
#Create a custom build step that will call ragel on the provided src_rl file.
#The output .cpp file will be appended to the variable name passed in outputlist.
get_filename_component(src_file ${src_proto} NAME_WE)
get_filename_component(src_path ${src_proto} PATH)
set(protobuf_out ${outputdir}/${src_path}/${src_file}.pb.cc)
#adding to the list inside a function takes special care, we cannot use list(APPEND...)
#because the results are local scope only
set(${outputlist} ${${outputlist}} ${protobuf_out} PARENT_SCOPE)
add_custom_command(
OUTPUT ${protobuf_out}
COMMAND protoc --cpp_out=${outputdir} -I${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${src_proto}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${src_proto}
)
set_source_files_properties(${protobuf_out} PROPERTIES GENERATED TRUE)
endfunction(protobufmaker)
function(sylar_add_executable targetname srcs depends libs)
add_executable(${targetname} ${srcs})
add_dependencies(${targetname} ${depends})
force_redefine_file_macro_for_sources(${targetname})
target_link_libraries(${targetname} ${libs})
endfunction()
然后在``CmakeList.txt`进行修改
# 加入-Wno-builtin-macro-redefined去掉警告
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O0 -g -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function -Wno-builtin-macro-redefined")
# 引入刚才的文件
include (cmake/utils.cmake)
# 哪个文件需要参数名就是谁
force_redefine_file_macro_for_sources(sylar) #__FILE__ 重定义该宏为相对路径
force_redefine_file_macro_for_sources(test) #__FILE__ 重定义该宏为相对路径
force_redefine_file_macro_for_sources(test_config) #__FILE__ 重定义该宏为相对路径
结果:
p20:日志系统的整合5
该节是日志系统的一个总结,到这里基础的日志系统就已经搭建好了,后续章节也会再次对其进行完善。