C++服务器框架:协程库——配置模块

主要用于定义/声明配置项,并且从配置文件中加载用户配置。

1 配置模块概述

主要用于定义/声明配置项,并且从配置文件中加载用户配置。一般而言,一项配置应该包括以下要素:

  • 名称:配置项的名称,对应一个字符串,必须唯一,不能与其他配置项产生冲突。

  • 类型:配置项的数据类型,可以是基本类型,但也应该支持复杂类型和自定义类型。

  • :配置项的值。

  • 默认值:配置项的默认值,考虑到用户不一定总是会显式地给配置项赋值,所以配置项最好有一个默认值。

  • 配置变更通知:一旦用户更新了配置值,那么应该通知所有使用了这项配置的代码,以便于进行一些具体的操作,比如重新打开文件,重新起监听端口等。

  • 校验方法:更新配置时会调用校验方法进行校验,以保证用户不会给配置项设置一个非法的值。

一个配置模块应具备的基本功能:

  • 支持定义/声明配置项:配置模块应该提供一种方法,允许用户声明和定义配置项。这包括提供配置的名称、数据类型以及可选的默认值。此外,应该支持在多个源文件中声明配置项,以确保在整个程序中都可以访问到这些配置。

  • 支持更新配置项的值:配置模块应该提供一种机制,允许用户更新配置项的值。这意味着用户可以通过代码动态地修改配置项的值,以满足程序运行时的需求。

  • 支持从预置途径中加载配置项:配置模块应该能够从预定义的途径(如配置文件、命令行参数、网络服务器等)加载配置项。这不仅包括基本数据类型的加载,还应该支持复杂数据类型的加载,如从配置文件中加载 map 类型的配置项或自定义结构体。

  • 支持注册配置变更通知:配置模块应该提供一种机制,允许用户注册配置变更通知。这意味着用户可以预先指定一个回调函数,当配置项发生变化时,配置模块会调用相应的回调函数通知用户,以便用户可以执行相应的操作。由于配置可能在多个地方引用,应该支持多个回调函数的注册。

  • 支持给配置项设置校验方法:配置模块应该允许用户为配置项设置校验方法,以确保配置项的值符合预期的规范。例如,用户可以为文件路径配置项设置一个校验方法,以确保指定的路径存在或符合特定的格式要求。

  • 支持导出当前配置:配置模块应该提供一种方法,允许用户导出当前的配置。这可以帮助用户将当前配置保存到文件中,或者用于程序的状态报告和调试。

配置模块的设计

采用约定优于配置的思想,简单来说,约定优于配置的背景条件是,程序中的许多配置项通常具有公认的默认值,即约定。例如,对于一个HTTP网络服务器,服务端口通常是80端口;对于配置文件夹路径,一般是"conf"文件夹;对于数据库目录,一般是"db""data"文件夹。由于这些配置具有公认的约定值,程序员无需在程序运行时逐项指定这些值,而是可以在程序初始化时将配置项设置为约定值。这样,程序员只需要修改那些超出约定范围的配置项,就能够以最小的代价使程序运行起来。

约定优于配置的方式能够减少程序员需要做的决定数量,获得简单的好处,同时也保持了一定的灵活性。因为程序员可以选择性地覆盖约定值,从而实现对程序行为的定制。

例如,通过以下方式设置协程栈大小,名称:fiber.stack_size,默认值:128 * 1024,描述:fiber stack size

1
2
static ConfigVar<uint32_t>::ptr g_fiber_stack_size = 
Config::Lookup<uint32_t>("fiber.stack_size", 128 * 1024, "fiber stack size");

当对YAML文件配置项做出改变时,也会改变相应的配置参数,此时,协程帧栈大小为256 * 1024256KB

1
2
fiber:
stack_size: 256 * 1024

关于YAML格式的介绍可参考YAML 入门教程

配置模块主要有以下几个类:

  • ConfigVarBase:配置项的基类,定义了配置项的公共成员和方法。它是一个虚基类,对每个配置项都包括名称和描述两项成员、以及toStringfromString纯虚函数方法。它并不包含配置项的类型和值,而是由继承类实现。

  • ConfigVar:具体的配置参数类,继承自ConfigVarBase,是一个模板类,具有三个模板参数。第一个模板参数是配置项的类型T,第二个和第三个模板参数FromStrToStr是仿函数,用于类型TYAML字符串之间的相互转换。这两个模板参数有默认值,根据不同的类型T有不同的偏特化实现。ConfigVar类包含了一个T类型的成员和一个变更回调函数数组。提供了setValuegetValue方法用于获取和更新配置值,并提供了addListenerdelListener方法用于添加或删除配置变更的回调函数。

  • ConfigConfigVar的管理类,负责管理所有的ConfigVar对象,采用单例模式。提供了Lookup方法,用于根据配置名称查询配置项。如果提供了默认值和描述信息,那么在未找到对应的配置项时,会自动创建一个对应的配置项。此外,Config类还提供了LoadFromYamlLoadFromConfDir方法,用于从YAML对象或命令行选项指定的配置文件路径中加载配置。Config类的所有成员变量和方法都是静态的,保证了全局只有一个实例。

这个配置模块的设计允许程序员定义并管理配置项,包括配置项的类型、默认值、描述信息等。同时提供了方便的方法来更新配置值、监听配置变更,并支持从不同来源加载配置。

2 ConfigVarBase

该类为抽象函数,提供三个纯虚函数供子类ConfigVar实现:

1
2
3
4
5
6
7
8
/// @brief 转成字符串
virtual std::string toString() = 0;

/// @brief 从字符串初始化值
virtual bool fromString(const std::string& val) = 0;

/// @brief 获取配置参数的类型名称
virtual std::string getTypeName() const = 0;

成员变量:

1
2
std::string m_name;         // 配置参数名称
std::string m_description; // 配置参数描述

2.1 成员函数

2.1.1 ConfigVarBase()

构造函数,初始化配置参数名称和描述,std::transform被用于将字符串m_name中的字母字符转换为小写形式并覆盖原来的字符串,所以不区分大小写。

1
2
3
4
5
ConfigVarBase(const std::string& name, const std::string& description = "")
:m_name(name)
,m_description(description) {
std::transform(m_name.begin(), m_name.end(), m_name.begin(), ::tolower);
}

3 ConfigVar

成员变量:

1
2
3
RWMutexType m_mutex;                    // 读写锁
T m_val; // 配置参数的值
std::map<uint64_t, on_change_cb> m_cbs; // 回调函数集合

3.1 类型转换

1
2
3
4
5
6
7
8
/* 
* T 参数的具体类型
* FromStr 从std::string转换成T类型的仿函数
* ToStr 从T转换成std::string的仿函数
* std::string 为YAML格式的字符串
*/
template <class T, class FromStr = LexicalCast<std::string, T>, class ToStr = LexicalCast<T, std::string>>
class ConfigVar : public ConfigVarBase { ... };

其中,FromStrToStr使用仿函数片特化的方式,实现不同类型Tstring之间的相互转化,例如vectorstring之间的转化,在转化的过程中,字符串格式都是以YAML为标准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/// 配置参数模板子类, F=FromType, T=ToType
template <class F, class T>
class LexicalCast {
public:
/**
* @brief 类型转换,重载()操作符
* @param[in] v 源类型值
* @return 返回v转换后的目标类型
* @exception 当类型不可转换时抛出异常
*/
T operator()(const F& v) {
return boost::lexical_cast<T>(v);
}
};

// - 1 ——> [1, 2, 3]
// - 2
// - 3
/// 配置参数模板子类, YAML String => std::vector<T>
template <class T>
class LexicalCast<std::string, std::vector<T>> {
public:
std::vector<T> operator()(const std::string& v) {
YAML::Node node = YAML::Load(v);
typename std::vector<T> vec;
std::stringstream ss;
for(size_t i = 0; i < node.size(); ++i) {
ss.str("");
ss << node[i];
vec.push_back(LexicalCast<std::string, T>()(ss.str()));
}
return vec;
}
};


// [1, 2, 3] ——> - 1
// - 2
// - 3
/// 配置参数模板子类, std::vector<T> => YAML String
template <class T>
class LexicalCast<std::vector<T>, std::string> {
public:
std::string operator()(const std::vector<T>& v) {
YAML::Node node(YAML::NodeType::Sequence); // 序列节点
for(auto& i : v) {
node.push_back(YAML::Load(LexicalCast<T, std::string>()(i)));
}
std::stringstream ss;
ss << node;
return ss.str();
}
};

除了vector类型外,还有listsetunordered_setmapunordered_map等类型的转化,这里不再赘述。

3.2 成员函数

3.2.1 ConfigVar()

构造函数,给配置参数赋值,初始化配置参数名称、默认值和描述。

1
2
3
ConfigVar(const std::string& name ,const T& default_value ,const std::string& description = "")
:ConfigVarBase(name, description)
,m_val(default_value) { }

3.2.2 toString()

若成功,返回转化后的string,失败打出日志,异常以及值的类型。

try {} 里面的代码可能会抛出异常,如果抛出异常,会被catch捕获,然后执行catch里面的代码

1
2
3
4
5
6
7
8
9
10
11
12
std::string toString() override {
try {
//return boost::lexical_cast<std::string>(m_val);
RWMutexType::ReadLock lock(m_mutex);
return ToStr()(m_val);
} catch (std::exception &e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::toString exception "
<< e.what() << " convert: " << TypeToName<T>() << " to string"
<< " name=" << m_name;
}
return "";
}

3.2.3 fromString()

YAML String初始化配置参数值,转换失败时抛出异常打印日志

1
2
3
4
5
6
7
8
9
10
11
bool fromString(const std::string& val) override {
try {
setValue(FromStr()(val));
} catch (std::exception &e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::fromString exception "
<< e.what() << " convert: string to " << TypeToName<T>()
<< " name=" << m_name
<< " - " << val;
}
return false;
}

3.2.4 getValue()

获取配置参数的值,加读锁保护

1
2
3
4
const T getValue() {
RWMutexType::ReadLock lock(m_mutex);
return m_val;
}

3.2.5 setValue()

设置配置参数的值,如果参数值发生变化,调用回调函数

1
2
3
4
5
6
7
8
9
10
11
void setValue(const T& v) {
{
RWMutexType::ReadLock lock(m_mutex);
if(v == m_val) return;
for(auto& i : m_cbs) {
i.second(m_val, v);
}
}
RWMutexType::WriteLock lock(m_mutex);
m_val = v;
}

3.2.6 getListener()

获取回调函数, key为回调函数的唯一id

1
2
3
4
5
on_change_cb getListener(uint64_t key) {
RWMutexType::ReadLock lock(m_mutex);
auto it = m_cbs.find(key);
return it == m_cbs.end() ? nullptr : it -> second;
}

3.2.7 addListener()

添加变更回调函数,返回该回调函数的唯一id,用于删除

1
2
3
4
5
6
7
uint64_t addListener(on_change_cb cb) {
static uint64_t s_fun_id = 0;
RWMutexType::WriteLock lock(m_mutex);
++s_fun_id;
m_cbs[s_fun_id] = cb;
return s_fun_id;
}

3.2.8 delListener()

删除回调函数

1
2
3
4
void delListener(uint64_t key) {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.erase(key);
}

3.2.9 clearListener()

清空所有回调函数

1
2
3
4
void clearListener() {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.clear();
}

4 Config

成员变量:

1
2
3
4
5
6
7
8
9
10
11
/// @brief 返回所有的配置项
static ConfigVarMap& GetDatas() {
static ConfigVarMap s_datas;
return s_datas;
}

/// @brief 配置项的读写锁
static RWMutexType& GetMutex() {
static RWMutexType s_mutex;
return s_mutex;
}

使用静态方法返回参数,保证初始化顺序

C++中,局部静态对象的生命周期是整个程序的,但是它们的初始化时机是在程序首次进入包含该静态对象定义的函数时进行的。

4.1 成员函数

4.1.1 Lookup(name, value, description)

用于获取或创建对应参数名的配置参数,如果参数名存在,返回对应的配置参数,否则创建一个新的配置参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
template <class T>
static typename ConfigVar<T>::ptr Lookup(const std::string& name, const T& default_value, const std::string& description = "") {
RWMutexType::WriteLock lock(GetMutex());
auto it = GetDatas().find(name);
if(it != GetDatas().end()) {
// 将ConfigVarBase转换为ConfigVar
auto tmp = std::dynamic_pointer_cast<ConfigVar<T>>(it -> second);
// 若转换成功,显示显示成功
if(tmp) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Lookup name=" << name << " exists";
return tmp;
} else {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name=" << name << " exists but type not "
<< TypeToName<T>() << " real_type=" << it->second->getTypeName()
<< " " << it->second->toString();
return nullptr;
}
}

// 用于在当前字符串中查找第一个不属于指定字符集合的字符,并返回该字符位置的索引。如果没有找到任何字符,则返回 std::string::npos。
// name不全在 "abcdefghigklmnopqrstuvwxyz._012345678" 中
// name中有非法字符,抛出异常
if(name.find_first_not_of("abcdefghijklmnopqrstuvwxyz._0123456789") != std::string::npos) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid " << name;
throw std::invalid_argument(name);
}

// 若没有,则创建一个新的ConfigVar
// typename:用于告诉编译器 ConfigVar<T>::ptr 是一个类型而不是成员变量。
typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, default_value, description));
GetDatas()[name] = v;
return v;
}

4.1.2 Loadup(name)

查找配置参数, 若找到参数名为name的配置参数,则返回对应的配置参数,否则返回nullptr

1
2
3
4
5
6
static typename ConfigVar<T>::ptr Lookup(const std::string& name) {
RWMutexType::ReadLock lock(GetMutex());
auto it = GetDatas().find(name);
if(it == GetDatas().end()) return nullptr;
return std::dynamic_pointer_cast<ConfigVar<T>>(it -> second);
}

4.1.3 LookupBase()

查找配置参数, 若找到参数名为name的配置参数,则返回对应的配置参数的基类(注意和上面函数的不同),否则返回nullptr

1
2
3
4
5
ConfigVarBase::ptr Config::LookupBase(const std::string &name) {
RWMutexType::ReadLock lock(GetMutex());
auto it = GetDatas().find(name);
return it == GetDatas().end() ? nullptr : it->second;
}

4.1.4 LoadFromYaml()

使用YAML::Node初始化配置模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void Config::LoadFromYaml(const YAML::Node &root) {
std::list<std::pair<std::string, const YAML::Node>> all_nodes;
ListAllMember("", root, all_nodes);

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());
}
}
}
}

LoadFromYaml函数首先调用ListAllMember函数,将YAML节点中的所有配置项提取出来,然后遍历所有的配置项,查找对应的配置参数,如果找到,则调用fromString函数初始化配置参数的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 递归方式,遍历YAML格式的配置文件中的所有成员,将每个节点的名称和值存在list中
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(g_logger) << "Config invalid name: " << prefix << " : " << node;
return;
}
output.push_back(std::make_pair(prefix, node));
// 若解析的是map
if (node.IsMap()) {
for (auto it = node.begin(); it != node.end(); ++it) {
// 若前缀为空,说明为顶层,prefix为key的值,否则为子层,prefix为父层加上当前层。it->second为当前node
ListAllMember(prefix.empty() ? it->first.Scalar() : prefix + "." + it->first.Scalar(), it->second, output);
}
}
}

4.1.5 Visit()

遍历所有的配置参数,调用回调函数。

1
2
3
4
5
6
7
void Config::Visit(std::function<void(ConfigVarBase::ptr)> cb) {
RWMutexType::ReadLock lock(GetMutex());
ConfigVarMap &datas = GetDatas();
for (auto it = datas.begin(); it != datas.end(); ++it) {
cb(it -> second);
}
}

5 配置与日志模块的结合

在项目中,配置模块和日志模块是紧密结合的,配置模块提供了一种机制,允许用户动态地修改日志模块的配置,以满足程序运行时的需求。例如,用户可以通过配置文件或命令行参数来修改日志的级别、输出格式、输出目的地等。

5.1 log.yaml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
logs:
- name: root
level: info
appenders:
- type: StdoutLogAppender
pattern: "%d{%Y-%m-%d %H:%M:%S} %T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
- name: system
level: info
appenders:
- type: StdoutLogAppender
- type: FileLogAppender
file: ./system.txt

5.2 LogAppender结构体重载==

定义日志输出器 结构体

1
2
3
4
5
6
7
8
9
struct LogAppenderDefine {
int type = 0; // 1: File, 2: Stdout
std::string file; // 文件路径
std::string pattern; // 日志格式

bool operator==(const LogAppenderDefine& oth) const {
return type == oth.type && file == oth.file && pattern == oth.pattern;
}
};

5.3 LogDefine结构体重载==<

定义日志器 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct LogDefine {
std::string name;
LogLevel::Level level = LogLevel::NOTSET;
std::vector<LogAppenderDefine> appenders;

bool operator==(const LogDefine& oth) const {
return name == oth.name && level == oth.level && appenders == oth.appenders;
}

bool operator<(const LogDefine& oth) const {
return name < oth.name;
}

bool isValid() const {
return !name.empty();
}
};

5.4 string TO LogDefine

YAML格式的字符串反序列化为一个LogDefine对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
template<>
class LexicalCast<std::string, LogDefine> {
public:
// 仿函数:类内部重载了()运算符,使得类的对象可以像函数一样被调用
LogDefine operator()(const std::string& v) {
// 将文本格式YAML数据v解析为内存中YAML节点node
YAML::Node node = YAML::Load(v);
LogDefine ld;
// 若未定义名为"name"的键,抛出异常
if(!node["name"].IsDefined()) {
std::cout << "log config error : name is null, " << node << std::endl;
throw std::logic_error("log config error : name is null");
}
// 将node["name"]值转为string赋给ld.name
ld.name = node["name"].as<std::string>();
// 若node定义了level,则直接赋值,否则为空
ld.level = LogLevel::FromString(node["level"].IsDefined() ? node["level"].as<std::string>() : "");
// 若n定义了appenders
if(node["appenders"].IsDefined()) {
// 遍历所有的appenders
for(size_t i = 0; i < node["appenders"].size(); i++) {
auto a = node["appenders"][i];
// 若当前appender没有定义type,输出错误日志
if(!a["type"].IsDefined()) {
std::cout << "log appender config error : appender type is null, " << a << std::endl;
continue;
}

std::string type = a["type"].as<std::string>();
// 定义LogAppenderDefine对象 lad
LogAppenderDefine lad;
// 若type为FileLogAppender
if(type == "FileLogAppender") {
lad.type = 1; // type置为1
// 若没有设置文件路径,输出错误日志
if(!a["file"].IsDefined()) {
std::cout << "log appender config error : file appender is null, " << a << std::endl;
continue;
}
// 设置文件路径
lad.file = a["file"].as<std::string>();
if(a["pattern"].IsDefined()) lad.pattern = a["pattern"].as<std::string>();
// 若type为STdoutLogAppender
} else if(type == "StdoutLogAppender") {
lad.type = 2; // type置为2
// 设置appender的formatter
if(a["pattern"].IsDefined()) lad.pattern = a["pattern"].as<std::string>();
} else { // type输入错误,输出错误
std::cout << "log appender config error : appender type is invalid, " << a << std::endl;
continue;
}
ld.appenders.push_back(lad);
} // end of for(size_t i = 0; i < node["appenders"].size(); i++)
} // end of if(node["appenders"].IsDefined())
return ld;
}
};

5.5 LogDefine TO string

将日志定义对象转换成一个YAML格式的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template<>
class LexicalCast<LogDefine, std::string> {
public:
std::string operator()(const LogDefine &i) {
YAML::Node n;
n["name"] = i.name;
n["level"] = LogLevel::ToString(i.level);
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.pattern.empty()) {
na["pattern"] = a.pattern;
}
n["appenders"].push_back(na);
}
std::stringstream ss;
ss << n;
return ss.str();
}
};

5.6 初始化Log

使用YAML配置文件初始化日志系统,并确保当日志配置发生变化时,系统能够相应地更新日志设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
sylar::ConfigVar<std::set<LogDefine>>::ptr g_log_defines = 
sylar::Config::Lookup("logs", std::set<LogDefine>(), "logs config");

struct LogIniter {
LogIniter() {
// 添加变化回调函数
g_log_defines -> addListener([](const std::set<LogDefine> &old_value, const std::set<LogDefine> &new_value){
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "on log config changed";
for(auto &i : new_value) {
auto it = old_value.find(i);
sylar::Logger::ptr logger;

if(it == old_value.end()) logger = SYLAR_LOG_NAME(i.name); // 新增logger
else {
if(!(i == *it))logger == SYLAR_LOG_NAME(i.name);// 修改的logger
else continue;
}
logger -> setLevel(i.level); // 设置level
logger -> clearAppenders();
// 设置appenders
for(auto &a : i.appenders) {
sylar::LogAppender::ptr ap;
if(a.type == 1) ap.reset(new FileLogAppender(a.file)); // FileLogAppender
else if(a.type == 2) { // StdoutLogAppender
// 如果以守护进程(daemon)方式运行,则不需要创建终端appender
if(!sylar::EnvMgr::GetInstance() -> has("d")) ap.reset(new StdoutLogAppender);
else continue;
}
// 设置appender的formatter
if(!a.pattern.empty()) ap -> setFormatter(LogFormatter::ptr(new LogFormatter(a.pattern)));
else ap -> setFormatter(LogFormatter::ptr(new LogFormatter));
logger -> addAppender(ap); // 添加appender
}
}

// 以配置文件为主,如果程序里定义了配置文件中未定义的logger,那么把程序里定义的logger设置成无效
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::NOTSET);
logger -> clearAppenders();
}
}
});
}
};

6 总结

通过YAML配置文件可以配置系统的参数,当设置新值时,可以通过回调函数更新系统配置。过程如下:

  1. main之前就通过static LogIniter __log_init;添加了变化回调函数。
  2. 使用YAML::Node root = YAML::LoadFile("sylar/bin/conf/log.yml");加载文件。
  3. 使用sylar::Config::LoadFromYaml(root)初始化配置模块,并会调用fromString()将解析出的nodestring转化为相应的类型,其中会调用setValue设置参数值并且调用变化回调函数更新logger的参数。