文章目录
- p9:配置模块搭建
- 一、ConfigvarBase
- 二、ConfigVar
- 三、Config
- 四、小结
- p10:YAML的使用
- 一、安装yaml-cpp
- 二、使用yaml-cpp
- 三、代码解析
- P11:YAML与日志的整合
- 一、方法函数
- 二、代码调试
- 三、test_config结果
- 四、小结
p9:配置模块搭建
很长时间没写了,又把上次的断点p9重新看了一次。
前面几个章节已经搭建好了日志系统的基本架构,从第九节开始搭建配置模块。该模块的主要用于定义/声明配置项,并且从配置文件中加载用户的配置。整个配置模块的目标就是与日志模块相结合,当配置文件相应参数做出改变时,能够通过回调函数改变相应的参数。
配置模块包含3个类
- ConfigvarBase:作为配置基类,放置一些公用的属性
- ConfigVar:配置参数模板子类,主要功能实现
string
和T类型
之间的相互转化 - Config:ConfigVar的管理类
一、ConfigvarBase
两个成员变量m_name
、m_description
分别定义配置参数的名称和描述,纯虚函数toString
和fromString
由子类ConfigVar
实现。
class ConfigvarBase {
public:
typedef std::shared_ptr<ConfigvarBase> ptr;
ConfigvarBase(const std::string name, const std::string description = "")
:m_name(name)
,m_description(description) {} // 构造函数
virtual ~ConfigvarBase() {} // 析构函数
const std::string& getName() const { return m_name; } // 返回配置参数名称
const std::string& getDescription() const { return m_description; } // 返回配置参数描述
virtual std::string toString() = 0; // 转换成字符串
virtual bool fromString(const std::string& val) = 0; // 从字符串初始化值
protected:
std::string m_name; // 配置参数的名称
std::string m_description; // 配置参数的描述
};
二、ConfigVar
m_val
是参数名对应的参数值。从构造函数可以看出目前的配置类主要包括配置名称、参数值以及配置描述3个变量。toString
和fromString
成员函数主要使用lexical_cast
进行了类型转换,如果转换失败会打印出日志、异常以及值的类型。
template<class T>
class ConfigVar : public ConfigvarBase {
public:
typedef std::shared_ptr<ConfigVar> ptr;
ConfigVar(const std::string& name, const T& default_value, const std::string& description = "") // 初始化配置名称、参数值以及参数描述
:ConfigvarBase(name,description)
,m_val(default_value) {
}
std::string toString() override { // 将参数值转换为string类型
try {
return boost::lexical_cast<std::string>(m_val);
} catch(std::exception& e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::toString exception"
<< e.what() << "convert: " << typeid(m_val).name() << " to string";
}
return "";
}
bool fromString(const std::string& val) override { // 从string转换为参数值
try {
m_val = boost::lexical_cast<T>(val);
} catch (std::exception& e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::fromString exception "
<< e.what() << " convert: string to " << typeid(m_val).name();
}
return false;
}
const T getValue() const { return m_val; }
void setValue(const T& v) { m_val = v; }
private:
T m_val;
};
三、Config
Config
是配置管理类,成员变量s_datas
使用static
是为了保证初始化顺序。成员函数Lookup则是查找目标配置项。
class Config {
public:
typedef std::map<std::string, ConfigvarBase::ptr> ConfigVarMap;
// 定义如果没有则初始化
template<class T>
static typename ConfigVar<T>::ptr Lookup(const std::string& name,
const T& default_value, const std::string& description = "") { // typename这里是告诉编译器::后面是类型,而不是变量名
auto tmp = Lookup<T>(name);
if(tmp) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Lookup nmae = " << name << " exists";
}
// 发现异常
if(name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._012345678") != std::string::npos) { // []
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid " << name;
throw std::invalid_argument(name);
}
// 无异常则定义
typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, default_value, description));
s_datas[name] = v;
return v;
}
// 查找
template<class T>
static typename ConfigVar<T>::ptr Lookup(const std::string& name) {
auto it = s_datas.find(name);
if(it == s_datas.end()) { // 未找到
return nullptr;
}
return std::dynamic_pointer_cast<ConfigVar<T>>(it->second); // 找到转换成智能指针
}
private:
static ConfigVarMap s_datas;
};
四、小结
以上就是整个配置类的一个初步架构,下面编写测试文件进行测试。
-
在tests文件夹创建test_config.cc
#include <iostream> #include "../sylar/log.h" #include "../sylar/util.h" #include"../sylar/config.h" sylar::ConfigVar<int>::ptr g_int_value_config = sylar::Config::Lookup("system.port", (int)8080, "system port"); // sylar::ConfigVar<float>::ptr g_float_value_config = // sylar::Config::Lookup("system.value", (float)10.2f, "system value"); int main(int argc, char** argv) { std::cout << g_int_value_config->getValue() << std::endl; SYLAR_LOG_INFO(SYLAR_LOG_ROOT()); SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_int_value_config->getValue(); // SYLAR_LOG_INFO刚刚报错是因为我在定义时getRoot没有加()进行调用 SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_int_value_config->toString(); // SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_float_value_config->getValue(); // SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_float_value_config->toString(); return 0; }
-
在CMakeLists.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 sylar/config.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) add_executable(test_config tests/test_config.cc) add_dependencies(test_config sylar) target_link_libraries(test_config sylar) SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
-
终端输入
make
编译 -
终端输入
bin/test_config
运行生成的可执行文件
p10:YAML的使用
本节内容主要是通过使用yaml-cpp库从yaml文件中读取配置信息,下面内容主要记录配置yaml的步骤以及对sylar使用yaml-cpp里面的方法进行一个简单解释。
一、安装yaml-cpp
-
从github上克隆仓库
git clone https://github.com/jbeder/yaml-cpp.git
,结果如下图所示,我是把克隆下来的仓库放在了项目外面,因为我们使用时不需要用到整个仓库所有的东西。 -
在克隆下来的仓库里面创建一个
build
文件夹,终端输入cd yaml-cpp
、mkdir build
-
进入创建的
build
文件夹。输入cmake -DBUILD_SHARED_LIBS=ON ..
,注意后面两个点不能忽略。然后得到如下内容,其中画横线的3个so文件就是我们需要的。-
最后开始安装,输入下列命令
make sudo make install
-
二、使用yaml-cpp
sylar这节演示的测试用例就是把一个yaml文件的内容遍历到控制台,上一步我们已经完成了yaml-cpp的安装,下面还需要项目文件了进行一些配置。
-
按照sylar的目录结构,在
bin
文件下创建一个conf
文件夹,然后再创建一个log.yml
文件,内容如下:logs: - name: root level: info formatter: '%d%T%m%n' appenders: - type: FileLogAppender file: /root/Web-learning/sylar/root.txt - type: StdoutLogAppender - name: system level: info formatter: '%d%T%m%n' appenders: - type: FileLogAppender file: /root/Web-learning/sylar/system.txt - type: StdoutLogAppender
-
进入我们刚才克隆下来编译后的yaml-cpp,首先在
build
文件夹找到下图3个文件把它们复制到sylar项目
lib
文件夹下,如下图所示:然后再回到
yaml-cpp
文件夹,找到里面的一个include
文件夹,也将它复制到sylar
中,如下: -
进入
CMakeLists.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") include_directories(.) # 这个不知道什么意思 include_directories(/root/Web-learning/sylar/include) # 这个路径需要按照你自己主机上include放的位置 link_directories(/root/Web-learning/sylar/lib) # 同样,根据自己主机lib的位置,必须保证该lib文件夹下有你刚刚复制的3个yaml-cpp的so库 find_library(YAMLCPP libyaml-cpp.a) # 待 set(LIB_SRC sylar/log.cc sylar/util.cc sylar/config.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) add_executable(test_config tests/test_config.cc) add_dependencies(test_config sylar) target_link_libraries(test_config sylar -L/root/Web-learning/sylar/lib -lyaml-cpp) # 待 SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
-
书写测试用例,进入
test_config.cc
#include <iostream> #include "../sylar/log.h" #include "../sylar/util.h" #include"../sylar/config.h" #include"yaml-cpp/yaml.h" sylar::ConfigVar<int>::ptr g_int_value_config = sylar::Config::Lookup("system.port", (int)8080, "system port"); // sylar::ConfigVar<float>::ptr g_float_value_config = // sylar::Config::Lookup("system.value", (float)10.2f, "system value"); void print_yaml(const YAML::Node& node, int level) { if(node.IsScalar()) { SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << std::string(level * 4, ' ') << node.Scalar() << " - " << node.Type() << " - " << level; } else if(node.IsNull()) { SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << std::string(level * 4, ' ') << "NULL - " << node.Type() << " - " << level; } else if(node.IsMap()) { for(auto it = node.begin(); it != node.end(); ++ it) { SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << std::string(level * 4, ' ') << it->first << " - " << it->second.Type() << " - " << level; print_yaml(it->second, level + 1); } } else if(node.IsSequence()) { for(size_t i = 0; i < node.size(); ++ i) { SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << std::string(level * 4, ' ') << i << " - " << node[i].Type() << " - " << level; print_yaml(node[i], level + 1); } } } void test_yaml() { YAML::Node root = YAML::LoadFile("/root/Web-learning/sylar/bin/conf/log.yml"); print_yaml(root, 0); } int main(int argc, char** argv) { // std::cout << g_int_value_config->getValue() << std::endl; SYLAR_LOG_INFO(SYLAR_LOG_ROOT()); SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_int_value_config->getValue(); // SYLAR_LOG_INFO刚刚报错是因为我在定义时getRoot没有加()进行调用 SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_int_value_config->toString(); // SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_float_value_config->getValue(); // SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_float_value_config->toString(); test_yaml(); return 0; }
-
结果展示
三、代码解析
简单介绍一下本节中使用的YAML里面的一些方法函数
-
YAML::LoadFile():接收一个文件路径,载入目标yml文件
-
node.IsScalar():判断当前是否为标量(可以理解为常量),从如下YAML
type.h
中,可以看到作者定义了5中不同类型,当我们读取自定义yml文件时就会根据当前遍历的值去判断应该做什么操作。 -
node.Type():对应上诉
NodeType
中的下标值,不过按照sylar的测试用例结果感觉有点不对,比如对于log.yml
中logs
的输出,logs
作为一个map
,它的下标应该对应4,然而结果node.Type()
输出的是3。(我理解错了,看最后一点) -
std::string(level * 4, ’ '):根据level的不同在输出前面加空格的数量,美化结果输出
-
对于node.Type()我刚刚提出的疑问,输出的
IsMap
是我在代码添加判断里加的,有一个问题就是当遇到Map
时,会进行遍历,而遍历的内容可能会出现其它类型,比如Scalar
或则Sequence
,然而我却在该Map
的条件判断里打印输出都加上了IsMap
,所以造成输出的type值与IsMap
不匹配。所以代码是没问题,之前理解错了。
P11:YAML与日志的整合
本节主要内容是YAML
与日志文件的整合,强烈建议学习本节之前花两分钟了解yaml
的格式。如果需要直接运行代码就看第三节。
一、方法函数
在配置管理类Config
中新增了两个成员函数LoadFromYaml
、LookupBase
,分别用于从yml
文件中载入配置内容和从配置内容中查找相应的目标项目。
LookupBase
接收一个字符串参数,在配置管理容器s_datas
中查询目标配置。s_datas
是ConfigVarMap
类型,它的定义也在下面。如果在容器中找到了目标,则返回一个ConfigvarBase
智能指针,包含目标配置参数的名称和描述
ConfigvarBase::ptr Config::LookupBase(const std::string& name) {
auto it = s_datas.find(name);
return it == s_datas.end() ? nullptr : it->second;
}
typedef std::map<std::string, ConfigvarBase::ptr> ConfigVarMap;
LoadFromYaml
代码解析我就直接放在注释中
void Config::LoadFromYaml(const YAML::Node& root) {
// 定义一个list,装载的元素是<string, node>类型
std::list<std::pair<std::string, const YAML::Node>> all_nodes;
// root中可以获取到日志里面配置的内容,ListAllMember则是把里面的内容分解成一个个节点,细节后面会讲
ListAllMember("", root, all_nodes);
// 检验ListAllMember的遍历结果是否正确
for(auto& i : all_nodes) {
std::string key = i.first;
if(key.empty()) continue;
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
ConfigvarBase::ptr var = LookupBase(key);
// 如果找到
if(var) {
if(i.second.IsScalar()) {
var->fromString(i.second.Scalar());
} else {
std::stringstream ss;
ss << i.second;
var->fromString(ss.str());
}
}
}
}
ListAllMember
该函数包含3个参数
- prefix:前缀,比如配置文件中的内容
name:root
,解析时prefix = name
,sylar在调用时传入的是一个空串,为了便于理解,我传入了一个null
,目的就是当遇到最外层的对象时,比如log.yml
中的logs:
,构造node时应该生成null : logs:
- node:表示一个
YAML
类型节点,如果当前的前缀合法,就把<prefix,node>放入结果list
:output
中 - output:存储结果容器
整个解析流程比较简单:
- 首先判断当前前缀是否合法
- 如果前缀合法则创建一个
pair
存入output
中 - 如果当前的
node
是一个对象,则继续遍历该对象里面的元素,递归调用ListAllMember
ListAllMember("null", root, all_nodes); // 调用,应用时null记得还原为空串
static void ListAllMember(const std::string& prefix,
const YAML::Node& node,
std::list<std::pair<std::string, const YAML::Node>>& output) {
// 判断prefix的合法性
if(prefix.find_first_not_of("abcdefghijklmnopqrstuvwxyz._012345678") != std::string::npos) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Prefixe name invalid " << prefix << " : " << node;
return;
}
output.push_back(std::make_pair(prefix, node));
// 如果node是一个对象
if(node.IsMap()) {
for(auto it = node.begin(); it != node.end(); ++ it) {
// 遍历对象里面的元素
ListAllMember(prefix.empty() ? it->first.Scalar() : prefix + "." + it->first.Scalar(), it->second, output);
}
}
}
二、代码调试
写完代码后还是不太理解ListAllMember
针对log.yml
构建节点的方式,然后通过gdb
调试有了一定的收获,不会调试的可以看看前面的文章,我都写得非常详细,这里就不列出调试的步骤,直接用实际数据演示过程。
对于log.yml
的数据(省略了一些),logs
是一个Map
,里面有两个数组,system
是一个对象,也就是可以通过system.port
获取相应元素的值
logs:
- name: root
level: info
formatter: '%d%T%m%n'
appenders:
- type: FileLogAppender
file: /root/Web-learning/sylar/root.txt
- type: StdoutLogAppender
- name: system
level: info
formatter: '%d%T%m%n'
appenders:
- type: FileLogAppender
file: /root/Web-learning/sylar/system.txt
- type: StdoutLogAppender
system:
port: 9900
value: 15
载入上诉文件,结果存储在all_nodes
,打印出来后结果如下
# 第一次打印,i.first = "", i.second = log.yml所有内容
logs:
- name: root
level: info
formatter: "%d%T%m%n"
appenders:
- type: FileLogAppender
file: /root/Web-learning/sylar/root.txt
- type: StdoutLogAppender
- name: system
level: info
formatter: "%d%T%m%n"
appenders:
- type: FileLogAppender
file: /root/Web-learning/sylar/system.txt
- type: StdoutLogAppender
system:
port: 9900
value: 15
0
# 第二次打印,i.first = "logs", i.second = logs里面的元素
logs- name: root
level: info
formatter: "%d%T%m%n"
appenders:
- type: FileLogAppender
file: /root/Web-learning/sylar/root.txt
- type: StdoutLogAppender
- name: system
level: info
formatter: "%d%T%m%n"
appenders:
- type: FileLogAppender
file: /root/Web-learning/sylar/system.txt
- type: StdoutLogAppender
1
# 第三次打印,i.first = "system", i.second = system里面的元素
systemport: 9900
value: 15
2
# 第三次打印,i.first = "system", i.second = system里面的元素
system.port9900
3
# 第四次打印,i.first = "system.port", i.second = 9900
system.value15
4
# 第四次打印,i.first = "system.value", i.second = 15
# 对于每个i.first,使用LookupBase查找打印结果
var.first: system.port var.second: 9900
var.first: system.value var.second: 15
在LoadFromYaml
中检验结果是否正确时回去all_nodes中查找,只有在查到system.port
和system.value
才能成功
三、test_config结果
在运行之前需要更改CMakeLists.txt
的内容,我放在下面了
CMakeLists.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")
include_directories(.)
include_directories(/root/Web-learning/sylar/include)
link_directories(/root/Web-learning/sylar/lib)
find_library(YAMLCPP yaml-cpp)
message("***", ${YAMLCPP})
set(LIB_SRC
sylar/log.cc
sylar/util.cc
sylar/config.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 ${YAMLCPP})
add_executable(test_config tests/test_config.cc)
add_dependencies(test_config sylar)
# target_link_libraries(test_config sylar -L/root/Web-learning/sylar/lib -lyaml-cpp)
target_link_libraries(test_config sylar ${YAMLCPP})
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
test_config
#include <iostream>
#include "../sylar/log.h"
#include "../sylar/util.h"
#include"../sylar/config.h"
#include"yaml-cpp/yaml.h"
sylar::ConfigVar<int>::ptr g_int_value_config =
sylar::Config::Lookup("system.port", (int)8080, "system port");
sylar::ConfigVar<float>::ptr g_float_value_config =
sylar::Config::Lookup("system.value", (float)10.2f, "system value");
void print_yaml(const YAML::Node& node, int level) {
if(node.IsScalar()) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << std::string(level * 4, ' ')
<< node.Scalar() << " - " << node.Type() << " - " << level;
} else if(node.IsNull()) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << std::string(level * 4, ' ') << "NULL - " << node.Type() << " - " << level;
} else if(node.IsMap()) {
for(auto it = node.begin(); it != node.end(); ++ it) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << std::string(level * 4, ' ')
<< it->first << " - " << it->second.Type() << " - " << level;
print_yaml(it->second, level + 1);
}
} else if(node.IsSequence()) {
for(size_t i = 0; i < node.size(); ++ i) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << std::string(level * 4, ' ')
<< i << " - " << node[i].Type() << " - " << level;
print_yaml(node[i], level + 1);
}
}
}
void test_yaml() {
YAML::Node root = YAML::LoadFile("/root/Web-learning/sylar/bin/conf/log.yml");
print_yaml(root, 0);
}
void test_config() {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "before:" << g_int_value_config->getValue();
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "before:" << g_float_value_config->toString();
YAML::Node root = YAML::LoadFile("/root/Web-learning/sylar/bin/conf/log.yml");
sylar::Config::LoadFromYaml(root);
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "after:" << g_int_value_config->getValue();
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "after:" << g_float_value_config->toString();
}
int main(int argc, char** argv) {
// test_yaml();
test_config();
return 0;
}
结果
可以看到,通过yml文件重新更改了配置参数的值。
四、小结
总的来说,本节最关键的函数就是ListAllMember
构建节点的过程,可以把它的作用简单理解为,想要构建诸如First = xx.xx Second = Val
,获取时就可以使用xx.xx = val
的形式。