模块概述
基于YAML配置文件实现的一个配置模块
- 难点:Lexical_cast的泛化与偏特化实现
- 设计点:
- 将配置项ConfigVar抽象出虚基类ConfigVarBase,使得Config可以通过多态机制统一管理配置类
- 通过Lexical_cast,将“支持更多的类型”,变为“支持更多类型的Lexcial_cast偏特化实现”
配置项概述
一项配置应该包括以下要素:
- 名称:对应一个字符串,必须唯一,不能与其他配置项产生冲突。支持名称的层级,如
tcp.connect.timeout
- 类型:支持基本类型、复杂类型(STL)和自定义类型。
- 值:配置项的值
- 默认值:考虑到用户不一定总是会显式地给配置项赋值,所以配置项最好有一个默认值(约定优于配置)
- 配置变更通知:一旦用户更新了配置值,那么应该通知所有使用了这项配置的代码,以便于进行一些具体的操作,比如重新打开文件,重新起监听端口等。
- 校验方法:更新配置时会调用校验方法进行校验,以保证用户不会给配置项设置一个非法的值。
配置模块功能概述
本项目配置模块具备的基本功能:
- 支持定义/声明配置项:也就是在提供配置名称、类型以及可选的默认值的情况下生成一个可用的配置项。
- 支持更新配置项的值:用户可以使用新的值来覆盖掉原来的值。
- 支持从预置的途径中加载配置项:一般是配置文件。支持基本数据类型与复杂数据类型的加载,比如直接从配置文件中加载一个map类型的配置项,或是直接从一个预定格式的配置文件中加载一个自定义结构体。
- 支持给配置项注册配置变更通知:用户可以为配置项注册回调函数,让程序知道某项配置被修改了,以便于进行一些操作。比如对于网络服务器而言,如果服务器端口配置变化了,那程序应该重新起监听端口。由于一项配置可能在多个地方引用,所以配置变更回调函数是一个数组的形式。
- 支持导出当前配置:将当前配置导出为YAML字符串
待实现的功能:
- 支持给配置项设置校验方法:配置项在定义时也可以指定一个校验方法,以保证该项配置不会被设置成一个非法的值,比如对于文件路径类的配置,可以通过校验方法来确保该路径一定存在。
- 从命令行参数或是网络服务器中加载配置项
Yaml
基本语法
Yaml书写方式比较简单,主要注意缩进问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| person: name: John Doe age: 30 address: street: 123 Main St city: Somewhere country: USA hobbies: - reading - hiking - coding
items: - apple - banana - orange
|
键值对(Mapping)
键值对使用 :
进行分隔,键和值之间用空格分开。
列表(Sequence)
列表元素前使用 -
符号进行标记。
1 2 3 4
| fruits: - apple - banana - cherry
|
嵌套结构
YAML 使用缩进来表示嵌套结构。通常使用2个空格作为缩进。
1 2 3 4 5 6
| person: name: Alice age: 28 address: street: 123 Main St city: Wonderland
|
注释以 #
开头,注释内容会被忽略。
多行字符串
使用 |
或 >
来表示多行字符串。
1 2 3 4 5 6 7 8
| description: | This is a multi-line string that keeps line breaks.
note: > This is a folded multi-line string that turns into a single line.
|
复杂数据类型
YAML 支持复杂数据类型,如时间、日期、布尔值等。
1 2
| date: 2024-12-25 boolean: true
|
yaml-cpp
项目中主要使用yaml-cpp来进行yaml文件的解析
依赖安装
1
| sudo apt install libyaml-cpp-dev
|
1 2 3 4 5 6
| git clone https://github.com/jbeder/yaml-cpp.git cd yaml-cpp mkdir build cd build cmake .. make && make install
|
用法
本项目中只使用部分Api,对于yaml-cpp掌握下面这些即可:
YAML::NodeType
yaml-cpp会自动解析Yaml字符串,并将解析结果变为以下几种YAML::NodeType:
- Scalar:简单类型
- Map:键值对
- Sequence:列表
Yaml的加载
- YAML::Load:从字符串中加载YAML::Node
- YAML::LoadFile:从文件中加载YAML::Node
配置模块设计
约定优于配置
约定优于配置的方式可以减少程序员做决定的数量,获得简单的好处,同时兼顾灵活性。
配置模块采用约定优于配置的思想,参考资料:
简单来说,约定优于配置的背景条件是,一般来说,程序所依赖的配置项都有一个公认的默认值,也就是所谓的约定。这点有可许多可以参考的例子,比如对于一个http网络服务器,服务端口通常都是80端口,对于配置文件夹路径,一般都是conf文件夹,对于数据库目录,一般都是db或data文件夹。对于这些具有公认约定的配置,就不需要麻烦程序员在程序跑起来后再一项一项地指定了,而是可以初始时就将配置项设置成对应的值。这样,程序员就可以**只修改那些约定之外的配置项,然后以最小的代价让程序跑起来**。
在代码上,约定优于配置的思路体现为所有的配置项在定义时都带一个的默认值,以下是一个sylar配置项的示例,这是一个int类型的配置项,名称为tcp.connect.timeout
,初始值为5000。
1 2
| static sylar::ConfigVar<int>::ptr g_tcp_connect_timeout = \ sylar::Config::Lookup("tcp.connect.timeout", 5000, "tcp connect timeout");
|
该配置项转换为YAML配置文件后如下:
1 2 3
| tcp: connect: timeout: 10000
|
模块中的类
ConfigVarBase
配置项抽象类,定义了配置项公有的成员和方法:
- 配置项的公有成员:
name
:配置项的名称
description
:配置项的描述
- 配置项的公有方法,两个纯虚函数,由派生类负责实现:
toString
:将配置项转换为YAML字符串
fromString
:从YAML字符串中载入配置项
ConfigVar:
每个配置项对应一个ConfigVar对象
是一个模板类,有三个模板参数:
T
:配置项的类型
FromStr
、ToStr
:两个仿函数,用于进行YAML字符串与配置项类型T之间的转换,是实现的重点,不同类型的配置项需要各自实现它们的偏特化版本
继承自ConfigVarBase(相当于定义了ConfigVarBase的一系列派生类),在其基础上实现了还需要实现以下功能:
- 对配置项数值的更新/获取操作:
setValue
/ getValue
- 对配置项的监听回调:
addListener
/ delListener
Config:
一个单例类(但单例的实现方式与普通的单例模式有所不同),使用map管理ConfigVar配置项
实现了以下方法:
Lookup
:可以在容器中查找/新增配置项
LoadFromYaml
:从YAML文件中载入配置项
模块实现
ConfigVarBase
抽象基类,简单定义了get/set方法,以及两个纯虚函数fromString
和toString
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
| class ConfigVarBase{ public: using ptr = std::shared_ptr<ConfigVarBase>;
ConfigVarBase(const std::string& name, const std::string& description = "") :m_name(name) ,m_description(description){ }
virtual ~ConfigVarBase(){}
std::string getName() const { return m_name; }
std::string getDescription() const { return m_description; }
virtual std::string getTypename() const = 0;
virtual std::string toString() = 0;
virtual bool fromString(const std::string& val) = 0;
private: std::string m_name; std::string m_description; };
|
ConfigVar
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
|
template<typename T, typename FromStr = Lexical_cast<std::string, T> , typename ToStr = Lexical_cast<T, std::string>> class ConfigVar: public ConfigVarBase{ public: using ptr = std::shared_ptr<ConfigVar>; using on_change_cb = std::function<void(const T& oldVal, const T& newVal)>; using RWMutexType = RWMutex;
ConfigVar(const std::string& name , const T& val , const std::string& description = "") : ConfigVarBase(name, description) , m_val(val){ }
std::string toString() override { try{ 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: " << typeid(m_val).name() << " to string"; } return ""; }
bool fromString(const std::string& val) override { try{ setValue(FromStr()(val)); return true; } catch(std::exception& e) { SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfiVar::fromString exception: " << e.what() << " convert string to " << typeid(m_val).name(); } return false; }
const T getValue() const { RWMutexType::ReadLock lock(m_mutex); return m_val; } void setValue(const T& val){ { RWMutexType::ReadLock lock(m_mutex); if(val == m_val){ return; } for(auto cb : m_cbs){ cb.second(m_val, val); } } RWMutexType::WriteLock lock(m_mutex); m_val = val; }
std::string getTypename() const override { return typeid(T).name(); }
uint64_t addListener(on_change_cb cb){ static uint64_t s_cbfun_id = 0;
RWMutexType::WriteLock lock(m_mutex); ++s_cbfun_id; m_cbs[s_cbfun_id] = cb; return s_cbfun_id; };
void delListener(uint64_t key){ RWMutexType::WriteLock lock(m_mutex); m_cbs.erase(key); };
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; };
void clearListener(){ RWMutexType::WriteLock lock(m_mutex); m_cbs.clear(); };
private: T m_val; std::map<uint64_t, on_change_cb> m_cbs; mutable RWMutexType m_mutex; };
|
多线程安全
由于配置项一般为读多写少,所以使用读写锁保证ConfigVar的线程安全
Lexical_cast
支持更多类型,在于支持更多类型的Lexical_cast特化版本
一个仿函数类,用于封装boost::lexical_cast
,支持ToStr
和FromStr
的实现
1 2
| typename FromStr = Lexical_cast<std::string, T>; typename ToStr = Lexical_cast<T, std::string>;
|
Lexical_cast泛化与特化两个版本
toString / fromString
调用模板传入的仿函数ToStr
/FromStr
即可,注意读写锁的上锁
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
| std::string toString() override { try{ 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: " << typeid(m_val).name() << " to string"; } return ""; }
bool fromString(const std::string& val) override { try{ setValue(FromStr()(val)); return true; } catch(std::exception& e) { SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfiVar::fromString exception: " << e.what() << " convert string to " << typeid(m_val).name(); } return false; }
|
回调函数
ConfigVar支持添加回调函数,并在配置项数值更新时自动通知所有的回调函数。ConfigVar使用一个map管理所有的回调函数,在添加回调函数时会自动分配一个id
1 2 3 4 5 6 7 8 9 10
| uint64_t addListener(on_change_cb cb){ static uint64_t s_cbfun_id = 0;
RWMutexType::WriteLock lock(m_mutex); ++s_cbfun_id; m_cbs[s_cbfun_id] = cb; return s_cbfun_id; };
|
setValue中配置项值更新完成后通知所有回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void setValue(const T& val){ { RWMutexType::ReadLock lock(m_mutex); if(val == m_val){ return; } for(auto cb : m_cbs){ cb.second(m_val, val); } } RWMutexType::WriteLock lock(m_mutex); m_val = val; }
|
Config
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| class Config{ public: using ConfigVarMap = std::unordered_map<std::string, ConfigVarBase::ptr>; using RWMutexType = RWMutex;
template<typename T> static typename ConfigVar<T>::ptr Lookup(const std::string& name){ RWMutexType::ReadLock lock(GetMutex());
auto it = GetDatas().find(name); if(it != GetDatas().end()){ return std::dynamic_pointer_cast<ConfigVar<T>>(it->second); } return nullptr; } template<typename T> static typename ConfigVar<T>::ptr Lookup(const std::string& name, const T& default_value, const std::string& description = ""){ auto& mp = GetDatas(); { RWMutexType::ReadLock lock(GetMutex()); auto it = mp.find(name); if(it != GetDatas().end()){ return Config::pointer_cast<T>(name, it->second); } }
if(name.find_first_not_of("1234567890._abcdefghijklmnopqrstuvwxyz") != std::string::npos){ SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid: " << name; throw std::invalid_argument(name); }
RWMutexType::WriteLock lock(GetMutex()); auto it = mp.find(name); if(it != mp.end()){ return Config::pointer_cast<T>(name, it->second); } typename ConfigVar<T>::ptr newConfigVar = std::make_shared<ConfigVar<T>>(name, default_value, description); mp[name] = newConfigVar; return newConfigVar; }
static ConfigVarBase::ptr LookupBase(const std::string& name);
static void LoadFromYaml(const YAML::Node& root);
static void Visit(std::function<void(ConfigVarBase::ptr)> cb);
private: template<typename T> static typename ConfigVar<T>::ptr pointer_cast(const std::string& name, ConfigVarBase::ptr val){ typename ConfigVar<T>::ptr tmp = std::dynamic_pointer_cast<ConfigVar<T>>(val);; if(tmp){ SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Lookup name=" << name << "exists."; return tmp; } else { SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name=" << name << ", but type not <" << typeid(T).name() << ">, real type=<" << val->getTypename() << ">"; } return nullptr; }
static ConfigVarMap& GetDatas(){ static ConfigVarMap s_datas; return s_datas; }
static RWMutexType& GetMutex(){ static RWMutexType s_mutex; return s_mutex; } };
|
单例类实现
Config的单例实现方式有所不同。它不通过Sington来实现单例,而是将所有的成员函数都设置为静态
注意静态数据成员变量也转变为通过静态成员函数获取
复习一下Effective C++条款4:

1 2 3 4 5 6 7 8 9 10 11
| static ConfigVarMap& GetDatas(){ static ConfigVarMap s_datas; return s_datas; } static RWMutexType& GetMutex(){ static RWMutexType s_mutex; return s_mutex; }
|
Lookup
Lookup用于向map中查找/添加配置项,整体逻辑比较简单。注意一下几点即可:
Visit
Visit
接收一个回调函数cb,对Config管理的所有配置项应用该回调函数
1 2 3 4 5 6 7
| void Config::Visit(std::function<void(ConfigVarBase::ptr)> cb){ RWMutexType::ReadLock lock(GetMutex()); auto& mp = GetDatas(); for(auto& i : mp){ cb(i.second); } }
|
LoadFromYaml
LoadFromYaml
实现从YAML::Node中载入配置
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
| void Config::LoadFromYaml(const YAML::Node& root){ std::list<std::pair<std::string, const YAML::Node>> all_nodes; ListAllMember("", root, all_nodes);
for(auto& item : all_nodes){ std::string key = item.first; if(key.empty()){ continue; }
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
ConfigVarBase::ptr var = LookupBase(item.first); if(var){ if(item.second.IsScalar()){ var->fromString(item.second.Scalar()); } else { std::stringstream ss; ss << item.second; var->fromString(ss.str()); } } } }
|
步骤:
将Yaml树结构拍平为list,便于后面遍历
text1 2 3
| tcp: connect: ----拍平----> tcp.connect.timeout timeout: 10000
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
static void ListAllMember(const std::string& prefix, const YAML::Node& node, std::list<std::pair<std::string, const YAML::Node>>& output){ if(prefix.find_first_not_of("1234567890._abcdefghijklmnopqrstuvwxyz") != std::string::npos){ SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid: " << prefix; throw std::invalid_argument(prefix); }
output.push_back(std::make_pair(prefix, node));
if(node.IsMap()){ for(auto& it : node){ ListAllMember(prefix.empty() ? it.first.Scalar() : prefix + "." + it.first.Scalar(), it.second, output); } } }
|
遍历YAML::Node,逐个解析配置项,有两个注意点
- 将配置项的键(名称)转换为全小写(大小写不敏感)
- 只有已经存在于Config中的配置项才会被载入
STL支持
支持更多类型,在于支持更多类型的Lexical_cast特化版本
- 实现方法:定义一个仿函数类Lexical_cast,以及它的一系列偏特化
- 由于Lexical_cast的递归调用,实现天生支持STL的嵌套
STL的实现方式分为以下两大类
- 将容器转化为
YAML::NodeType::Sequence
- 包括vector、list、set、unordered_set
- 将容器转化为
YAML::NodeType::Map
- map、unordered_map(目前的实现只支持key为std::string)
vector、list、set、unordered_set
转化为YAML::NodeType::Sequence
,以vector为例:
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
| template<typename T> class Lexical_cast<std::vector<T>, std::string>{ public: std::string operator()(const std::vector<T> vec){ YAML::Node node(YAML::NodeType::Sequence); for(auto& i : vec){ node.push_back(YAML::Load(Lexical_cast<T, std::string>()(i))); } std::stringstream ss; ss << node; return ss.str(); } };
template<typename T> class Lexical_cast<std::string, std::vector<T>>{ public: std::vector<T> operator()(const std::string str){ YAML::Node nodes = YAML::Load(str); std::vector<T> vec; std::stringstream ss; for(const auto& node : nodes){ ss.str(""); ss << node; vec.push_back(Lexical_cast<std::string, T>()(ss.str())); } return vec; } };
|
map、unordered_map
转化为YAML::NodeType::Map
,以map为例:
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<typename T> class Lexical_cast<std::map<std::string, T>, std::string>{ public: std::string operator()(const std::map<std::string, T> mp){ YAML::Node node(YAML::NodeType::Map); for(auto& i : mp){ node[i.first] = YAML::Load(Lexical_cast<T, std::string>()(i.second)); } std::stringstream ss; ss << node; return ss.str(); } };
template<typename T> class Lexical_cast<std::string, std::map<std::string, T>>{ public: std::map<std::string, T> operator()(const std::string str){ YAML::Node nodes = YAML::Load(str); std::map<std::string, T> mp; std::stringstream ss; for(const auto& node : nodes){ ss.str(""); ss << node.second; mp.insert(std::make_pair(node.first.Scalar(), Lexical_cast<std::string, T>()(ss.str()))); } return mp; } };
|
自定义类型支持
同STL,只要提供自定义类型的fromString和toString,就可以让配置系统支持该自定义类型
例子:
类定义
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
| class Person{ public: Person(const std::string& name = "", int age = 0, bool sex = false) : m_name(name), m_age(age), m_sex(sex){ }
std::string toString() const { std::stringstream ss; ss << "[Person: name=" << m_name << ", age=" << m_age << ", sex=" << (m_sex ? "female" : "male") << "]"; return ss.str(); }
std::string getName() const { return m_name; } int getAge() const { return m_age; } bool getSex() const { return m_sex; }
bool operator==(const Person& oth) const { return m_name == oth.m_name && m_age == oth.m_age && m_sex == oth.m_sex; }
private: std::string m_name; int m_age; bool m_sex; };
|
fromString和toString
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
| namespace sylar{
template<> class Lexical_cast<std::string, Person>{ public: Person operator()(const std::string& val){ YAML::Node node = YAML::Load(val); Person p(node["name"].as<std::string>(), node["age"].as<int>(), node["sex"].as<bool>()); return p; } };
template<> class Lexical_cast<Person, std::string>{ public: std::string operator()(const Person& val){ YAML::Node node(YAML::NodeType::Map); node["name"] = val.getName(); node["age"] = val.getAge(); node["sex"] = val.getSex(); std::stringstream ss; ss << node; return ss.str(); } };
}
|
使用
1 2 3 4 5 6 7
| sylar::ConfigVar<Person>::ptr g_person_config = sylar::Config::Lookup("class.person", Person("y", 0, false), "Person class");
sylar::ConfigVar<std::map<std::string, Person>>::ptr g_person_map_config = sylar::Config::Lookup("class.persons", std::map<std::string, Person>({{"nobody", Person()}}), "Person class map");
|
模块使用
载入配置项
1 2 3 4
| YAML::Node root = YAML::LoadFile("/conf/test.yml");
sylar::Config::LoadFromYaml(root);
|
添加/获取配置项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| sylar::ConfigVar<int>::ptr g_int_value_config = sylar::Config::Lookup<int>("system.port", 8080, "system port");
sylar::ConfigVar<std::vector<int> >::ptr g_int_vec_value_config = sylar::Config::Lookup("system.int_vec", std::vector<int>{1, 2}, "system int vector");
sylar::ConfigVar<std::list<int> >::ptr g_int_list_value_config = sylar::Config::Lookup("system.int_list", std::list<int>{1, 2}, "system int list");
sylar::ConfigVar<std::set<int> >::ptr g_int_set_value_config = sylar::Config::Lookup("system.int_set", std::set<int>{1, 2}, "system int set");
sylar::ConfigVar<std::unordered_set<int> >::ptr g_int_uset_value_config = sylar::Config::Lookup("system.int_uset", std::unordered_set<int>{1, 2}, "system int unordered_set");
sylar::ConfigVar<std::map<std::string, int> >::ptr g_str_int_map_value_config = sylar::Config::Lookup("system.str_int_map", std::map<std::string, int>{{"k",2}}, "system str int map");
sylar::ConfigVar<std::unordered_map<std::string, int> >::ptr g_str_int_umap_value_config = sylar::Config::Lookup("system.str_int_umap", std::unordered_map<std::string, int>{{"k",2}}, "system str int map");
|
与日志系统的整合
LogAppenderDefine
LogAppenderDefine
为定义Appender信息的结构体,结构体需要包含以下内容:
type
:表示Appender的类型,目前日志系统支持输出到终端/文件两种appender,type取值如下:
pattern
:表示输出格式的模板
file
:在type == 1时有效,记录输出文件的路径
operator==
:为配置系统提供结构体的比较方法,上面三个数据成员全相等才返回true
1 2 3 4 5 6 7 8 9 10 11
| struct LogAppenderDefine{ int type = 0; std::string pattern; std::string file;
bool operator==(const LogAppenderDefine& oth) const { return type == oth.type && pattern == oth.pattern && file == oth.file; } };
|
LoggerDefine
LoggerDefine
为记录Logger信息的结构体,结构体需要包含以下内容:
name
:Logger的名称
level
:Logger的级别,类型为LogLevel::Level
appenders
:管理LogAppenderDefine结构体的数组
- 重载关系运算符,包括operator==、operator!=、operator<
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct LoggerDefine{ std::string name; LogLevel::Level level = LogLevel::UNKNOW; std::vector<LogAppenderDefine> appenders;
bool operator==(const LoggerDefine& oth) const { return name == oth.name && level == oth.level && appenders == oth.appenders; }
bool operator!=(const LoggerDefine& oth) const { return !(*this == oth); }
bool operator<(const LoggerDefine& oth) const { return name < oth.name; } };
|
Lexcial_cast偏特化
实现Lexical_cast偏特化,使用YAML::NodeType::Map管理。整体逻辑不难理解,注意一下YAML::Node的操作方法即可
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
|
template<> class Lexical_cast<std::string, LoggerDefine>{ public: LoggerDefine operator()(const std::string& val){ YAML::Node node = YAML::Load(val); LoggerDefine ld; if(!node["name"].IsDefined()){ std::cerr << "log config error: name is null" << node << std::endl; throw std::logic_error("log config name is null"); } ld.name = node["name"].as<std::string>(); ld.level = LogLevel::FromString(node["level"].IsDefined() ? node["level"].as<std::string>() : ""); if(node["appenders"].IsDefined()){ for(auto a : node["appenders"]){ if(!a["type"].IsDefined()){ std::cerr << "log config error: appender type is null" << a << std::endl; continue; } LogAppenderDefine lad;
std::string type = a["type"].as<std::string>(); if(type == "FileLogAppender"){ lad.type = 1; if(!a["file"].IsDefined()){ std::cerr << "log config error: FileLogAppender filename is null" << a << std::endl; continue; } lad.file = a["file"].as<std::string>(); } else if(type == "StdoutLogAppender"){ lad.type = 2; } else { std::cerr << "log config error: appender type is invalid" << a << std::endl; continue; } if(a["pattern"].IsDefined()){ lad.pattern = a["pattern"].as<std::string>(); } ld.appenders.push_back(lad); } } return ld; } };
template<> class Lexical_cast<LoggerDefine, std::string>{ public: std::string operator()(const LoggerDefine& ld){ YAML::Node node(YAML::NodeType::Map); node["name"] = ld.name; node["level"] = LogLevel::ToString(ld.level); std::stringstream ss_appenders; for(const auto& lad : ld.appenders){ YAML::Node nlad(YAML::NodeType::Map); if(lad.type == 1){ nlad["type"] = "FileLogAppender"; nlad["file"] = lad.file; } else if (lad.type == 2){ nlad["type"] = "StdoutLogAppender"; } if(!lad.pattern.empty()){ nlad["pattern"] = lad.pattern; } node["appenders"].push_back(nlad); } std::stringstream ss; ss << node; return ss.str(); } };
|
日志模块配置项g_log_config
在log.h中注册日志模块的配置项:
1 2 3
| ConfigVar<std::set<LoggerDefine>>::ptr g_log_config = Config::Lookup("logs", std::set<LoggerDefine>(), "logs config");
|
回调函数
**全局对象在main函数前构造(其构造函数在main函数之前触发)**,我们可以利用这一特性为 g_log_config 配置项添加回调函数
回调函数接收两个set,一个为配置项旧值oldVal
,一个为新值newVal
。对比配置项的新旧值,完成logger的创建 / 修改 / 删除工作:
- 创建 / 修改:创建/获取
newVal
中对应的日志器,根据LoggerDefine的新值设置日志器属性
- 删除:如果日志器在
oldVal
中但不在newVal
中,则说明该日志器需要被删除。进行的操作为逻辑删除(将Level设置为无法访问到的级别,并情况其appender)
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
| struct LogIniter{ LogIniter(){ g_log_config->addListener([](const std::set<LoggerDefine>& oldVal, const std::set<LoggerDefine>& newVal){ for(auto& i : newVal){ auto it = oldVal.find(i); Logger::ptr logger; if(it == oldVal.end()){ logger = SYLAR_LOG_NAME(i.name); } else { if(i != *it){ logger = SYLAR_LOG_NAME(i.name); } else { continue; } } logger->setLevel(i.level); logger->clearAppenders(); for(auto &a : i.appenders){ LogAppender::ptr ap; if(a.type == 1){ ap.reset(new FileLogAppender(a.file)); } else if(a.type == 2){ ap.reset(new StdoutLogAppender()); } if(!a.pattern.empty()){ ap->setFormatter(LogFormatter::ptr(new LogFormatter(a.pattern))); } logger->addAppender(ap); } }
for(auto& i : oldVal){ auto it = newVal.find(i); if(it == newVal.end()){ Logger::ptr logger = SYLAR_LOG_NAME(i.name); logger->setLevel(LogLevel::NOTSET); logger->clearAppenders(); } } }); } };
static LogIniter __log_init;
|
配置文件
日志系统的配置系统可以如下形式:
1 2 3 4 5 6 7 8
| logs: - name: root level: info appenders: - type: FileLogAppender file: /home/mySylar/log.txt - type: StdoutLogAppender pattern: "%d%T%m%n"
|