配置模块概述
配置模块采用约定优于配置的设计思想,让程序所依赖的配置项都有一个默认值,就不需要每次都指定了,这样既简单又灵活。例如通过以下方式设置默认端口号,名称:system.port
,值:8080
,描述:system port
sylar::ConfigVar<int>::ptr g_int_value_config =
sylar::Config::Lookup("system.port", (int)8080, "system port");
当对YAML
文件配置项做出改变时,也会改变相应的配置参数,此时端口号被修改为5050
system:
port: 5050
YAML支持以下几种数据类型:
- 对象(Map ) :类似标准库中的Map,键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)。(key: value)形式出现的数据
port: 5050
# 支持多层嵌套
system:
port: 5050
addr: 127.0.0.1
# 支持流式风格
system: { port: 5050, addr: 127.0.0.1 }
- 数组(Sequence ) :一组按次序排列的值,又称为序列(sequence) / 列表(list),一组以区块格式(Block Format) (即“破折号+空格”) 开头的数据组成一个数组
values:
- value1
- value2
- value3
# 支持多维数组(用缩进表示层级关系)
values:
-
- value1
- value2
-
- value3
- value4
- 纯量(scalars) :单个的、不可再分的值
字符串
布尔值
整数
浮点数
Null
时间
日期
配置模块主要有以下几个类:
- class ConfigVarBase:配置变量的基类
- class ConfigVar:配置参数模板子类,保存对应类型的参数值,通过仿函数实现
string
和T类型
之间的相互转化 - class Config: ConfigVar的管理类
最后将日志模块与配置模块整合起来,当配置文件相应参数做出改变时,能够通过回调函数改变相应的参数
详解
class ConfigVarBase(配置变量的基类)
该类为抽象函数,提供三个纯虚函数供子类ConfigVar
实现
virtual std::string toString() = 0; //转化为string
virtual bool fromString(const std::string& val) = 0; //从string转化为相应类型
virtual std::string getTypeName() const = 0; //获得该类型的名称
mumber(成员变量)
// 名字
std::string m_name;
// 描述
std::string m_description;
ConfigVarBase(构造函数 )
std::transform
被用于将字符串m_name
中的字母字符转换为小写形式并覆盖原来的字符串。所以不区分大小写
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);
}
class ConfigVar(配置参数)
/*
* 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 {};
其中,FromStr
和ToStr
使用仿函数片特化的方式,实现不同类型T
与string
之间的相互转化,例如vector
与string
之间的转化,在转化的过程中,字符串格式都是以YAML
为标准。
//F from_type, T to_type
template<class F, class T>
class LexicalCast {
public:
T operator() (const F &v) {
return boost::lexical_cast<T>(v);
}
};
// string To vector
// "[1, 2, 3]" ——> [1, 2, 3]
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;
}
};
// vector To string
// [1, 2, 3] ——> - 1
// - 2
// - 3
template<class T>
class LexicalCast<std::vector<T>, std::string> {
public:
std::string operator() (const std::vector<T>& v) {
YAML::Node node;
for (auto& i : v) {
node.push_back(YAML::Load(LexicalCast<T, std::string>()(i)));
}
std::stringstream ss;
ss << node;
return ss.str();
}
};
mumber(成员变量)
// 参数值
T m_val;
// 变更回调函数组, uint64_t key(要求唯一,一般可以用hash)
// typedef std::function<void(const T &old_value, const T &new_value)> on_change_cb;
std::map<uint64_t, on_change_cb> m_cbs;
// 读写锁
mutable RWMutexType m_mutex;
ConfigVar(构造函数)
给配置名
、描述
、参数值
赋值
ConfigVar(const std::string& name
, const T& defult_val
, const std::string& description = "")
: ConfigVarBase(name, description)
, m_val(defult_val) {
}
toString(从参数转为string)
若成功,返回转化后的string
,失败打出日志,异常以及值的类型
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 "";
}
fromString(从string转为值)
从YAML String
转为参数值,失败打出日志,异常以及值的类型
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 " << typeid(m_val).name()
<< " - " << val;
}
return false;
}
getValue(获取)和setValue(设置参数)
获取参数直接返回m_val
,设置参数时,判断v
与m_val
是否相同,若相同,直接返回,若不相同,则回调变更函数
// 获取参数
const T getValue() const {
RWMutexType::ReadLock lock(m_mutex);
return m_val;
}
// 设置参数
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;
}
addListener(添加变化回调函数)
返回该回调函数对应的唯一id,用于删除回调函数
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;
}
delListener(删除回调函数)
void delListener(uint64_t key) {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.erase(key);
}
getListener(获取回调函数)
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;
}
clearListener(清除回调函数)
void clearListener() {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.clear();
}
class Config(ConfigVar管理类)
mumber(成员变量)
使用静态方法返回参数,保证初始化顺序
c++保证,函数内的local static
对象会在”该函数被调用期间“”首次遇上该对象之定义式“时被初始化。
// typedef std::unordered_map<std::string, ConfigVarBase::ptr> ConfigVarMap;
// 返回所有的配置项
static ConfigVarMap& GetDatas() {
static ConfigVarMap s_datas;
return s_datas;
}
// 配置项的RWMutex
static RWMutexType& GetMutex() {
static RWMutexType s_mutex;
return s_mutex;
}
Lookup(获取/创建对应参数名的配置参数)
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 << " exitst but type not "
<< typeid(T).name() << ", real_type = " << it->second->getTypeName()
<< " " << it->second->toString();
return nullptr;
}
}
// 用于在当前字符串中查找第一个不属于指定字符集合的字符,并返回该字符位置的索引。如果没有找到任何字符,则返回 std::string::npos。
// name不全在 "abcdefghigklmnopqrstuvwxyz._012345678" 中
// name中有非法字符,抛出异常
if (name.find_first_not_of("abcdefghigklmnopqrstuvwxyz._012345678")
!= 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;
}
Lookup(查找配置参数)
template<class 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 nullptr;
}
return std::dynamic_pointer_cast<ConfigVar<T>>(it->second);
}
LookupBase(查找配置参数,返回配置参数的基类)
ConfigVarBase::ptr Config::LookupBase(const std::string& name) {
RWMutexType::ReadLock lock(GetMutex());
auto it = GetDatas().find(name);
return it == GetDatas().end() ? nullptr : it->second;
}
LoadFromYaml(使用YAML::Node初始化配置模块)
// 递归方式,遍历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("abcdefghigklmnopqrstuvwxyz._012345678")
!= std::string::npos) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "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);
}
}
}
void Config::LoadFromYaml(const YAML::Node& root) {
// 将root中的所有节点信息保存到all_nodes中
std::list<std::pair<std::string, const YAML::Node> > all_nodes;
ListAllMember("", root, all_nodes);
// 遍历list
for(auto &i : all_nodes) {
std::string &key = i.first;
if (key.empty()) {
continue;
}
// 无视大小写
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
// 查找名为key的配置参数
ConfigVarBase::ptr var = LookupBase(key);
// 若找到
if (var) {
// 若为纯量,则调用fromString(会调用setValue设值)
if (i.second.IsScalar()) {
var->fromString(i.second.Scalar());
// 否则为数组,将其转换为字符串
} else {
std::stringstream ss;
ss << i.second;
var->fromString(ss.str());
}
}
}
}
Visit(遍历配置模块里面所有配置项)
void Config::Visit(std::function<void(ConfigVarBase::ptr)> cb) {
RWMutexType::ReadLock lock(GetMutex());
ConfigVarMap& m = GetDatas();
for (auto it = m.begin();
it != m.end(); ++it) {
cb(it->second);
}
}
Log和Config整合
log.yml内容格式如下
logs:
- name: root
level: info
formatter: "%d%T%m%n"
appenders:
- type: FileLogAppender
file: root.txt
- type: StdoutLogAppender
- name: system
level: debug
formatter: "%d%T%m%n"
appenders:
- type: FileLogAppender
file: system.txt
formatter: "%d%T[%p]%T%m%n"
- type: StdoutLogAppender
LogAppender结构体重载==
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;
}
};
Log结构体,重载==
、<
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;
}
};
string
To LogDefine
template<>
class LexicalCast<std::string, LogDefine> {
public:
// 仿函数
LogDefine operator()(const std::string& v) {
// 将文本格式YAML数据v解析为内存中YAML节点n
YAML::Node n = YAML::Load(v);
LogDefine ld;
// 若未定义名为"name"的键,抛出异常
if(!n["name"].IsDefined()) {
std::cout << "log config error: name is null, " << n
<< std::endl;
throw std::logic_error("log config name is null");
}
// 将n["name"]值转为string赋给ld.name
ld.name = n["name"].as<std::string>();
// 若n定义了level,则直接赋值,否则为空
ld.level = LogLevel::FromString(n["level"].IsDefined() ? n["level"].as<std::string>() : "");
// 若n定义了formatter,则使用当前值,否则为默认的formatter
if(n["formatter"].IsDefined()) {
ld.formatter = n["formatter"].as<std::string>();
}
// 若n定义了appenders
if(n["appenders"].IsDefined()) {
// 遍历所有的appenders
for(size_t x = 0; x < n["appenders"].size(); ++x) {
auto a = n["appenders"][x];
// 若当前appender没有定义type,输出错误日志
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
LogAppenderDefine lad;
// 若type为FileLogAppender
if(type == "FileLogAppender") {
// type置为1
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>();
// 设置appender的formatter
if(a["formatter"].IsDefined()) {
lad.formatter = a["formatter"].as<std::string>();
}
// 若type为STdoutLogAppender
} else if(type == "StdoutLogAppender") {
// type置为2
lad.type = 2;
// 设置appender的formatter
if(a["formatter"].IsDefined()) {
lad.formatter = a["formatter"].as<std::string>();
}
// type输入错误
} else {
std::cout << "log config error: appender type is invalid, " << a
<< std::endl;
continue;
}
ld.appenders.push_back(lad);
}
}
return ld;
}
};
LogDefine
To string
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();
}
};
按照YAML配置文件初始化Log
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_logger_conf_changed";
for(auto& i : new_value) {
auto it = old_value.find(i);
sylar::Logger::ptr logger;
// new有 old没有
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;
}
}
// 设置level
logger->setLevel(i.level);
// 设置默认formatter
if (!i.formatter.empty()) {
logger->setFormatter(i.formatter);
}
logger->clearAppenders();
// 设置appenders
for (auto &a : i.appenders) {
sylar::LogAppender::ptr ap;
// File
if (a.type == 1) {
ap.reset(new FileLogAppender(a.file));
// Stdout
} else if (a.type == 2) {
ap.reset(new StdoutLogAppender);
}
// 设置appenderlevel
ap->setLevel(a.level);
// 设置appender的formatter
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的appender中
logger->addAppender(ap);
}
}
for(auto& i : old_value) {
auto it = new_value.find(i);
// old有 new没有
if(it == new_value.end()) {
//删除logger
auto logger = SYLAR_LOG_NAME(i.name);
logger->setLevel((LogLevel::Level)100);
logger->clearAppenders();
}
}
});
}
};
// 保证在main之前初始化
static LogIniter __log_init;
总结
通过YAML
配置文件可以配置系统的参数,当设置新值时,可以通过回调函数更新系统配置。举个实例讲述一下过程。
- 在main之前就通过
static LogIniter __log_init;
添加了变化回调函数 - 使用
YAML::Node root = YAML::LoadFile("sylar/bin/conf/log.yml");
加载文件 - 使用
sylar::Config::LoadFromYaml(root)
初始化配置模块,并会调用fromString()
将解析出的node
从string
转化为相应的类型,其中会调用setValue
设置参数值并且调用变化回调函数更新logger
的参数。