配置系统
ConfigVarBase 和 ConfigVar
每个配置项,由多个 key,value 和 description 组成。这三个可以由 string 保存,每个 value 对应着多个类型(可以是 int,float,bool,自定义类型...)。 那么,基类 ConfigVarBase 声明 key 和 description,以及纯虚函数的析构,toString,fromString。(toString, fomString 分别对应着 value 从 string -> T 反序列化,以及 T-> string 序列化)
- 对应 基础类型 的 设计,可以直接使用 boost 库里的 boost::lexical_cast(F); 从 F 类型转换到 T 类型。为了方便下一步的设计,设计模板类LexicalCast。
- 对于 满足 stl 设计,例如:转换成 vector ,vector,set,map。同时要兼容 yaml-cpp 库的设计( Node 和 string 的相互转换)。使用LexicalCast 的 容器特化
cpp
// 基类
calss ConfigVarBase{
public:
vtual ~ConfigVarBase() = 0;
virtual std::string toString() = 0;
virtual bool fromString(const std::string& val) = 0;
private:
std::string m_name;
std::string m_description;
};
cpp
template<class F, class T>
class LexicalCast{
public:
T operator()(const F& val){
return boost::lexical_cast<T>(val);
}
};
// 这里简单使用vector和map举个例子
// 从string转vector<T>, 其中string是在读取yaml的时候,直接对Node转成string的,所以这里可以使用YAML::Laod(str)进行恢复。
template<class T>
class LexicalCast<std::string, class std::vector<T> >{
public:
std::vector<T> operator()(const std::string& val){
YAML::Node node = YAML::Load(val);
std::vector<T> v;
std::stringstream ss;
for(size_t i = 0 ; i < node.size() ; ++i){
ss.str();
ss << node[i];
v.push_back(LexicalCast<std::string, T>()(ss.str()));
}
return v;
}
};
template<class T>
class LexicalCast<class std::vector<T>, class std::string>{
public:
std::string operator()(const std::vector<T>& val){
YAML::Node node;
for(auto& it : val){
node.push_back(YAML::Load(LexicalCast<T, std::string>()(it);
}
std::stringstream ss;
ss << node;
return ss.str();
}
};
template<class T>
class LexicalCast<class std::string, class std::map<std::string, T> >{
public:
std::map<std::string, T> operator()(const std::string& val){
YAML::Node node = YAML::Load(val);
std::map<std::string, T> v;
std::stringstream ss;
for(auto it = node.begin() ; it != node.end() ; ++it){
ss.str();
ss << it->second;
v.insert(make_pair<std::string, T>(it->first.Scalar(), LexicalCast<std::string, T>(ss.str())));
}
return v;
}
};
template<class T>
class LexicalCast<class std::map<std::string, T>, class std::string>{
public:
std::string operator()(const std::map<std::string, T>& val){
YAML::Node node;
for(auto& it : val){
node[it.first] = LexicalCast<T, std::string>()(it.second);
}
std::stringstream ss;
ss << node;
return ss.str();
}
};
cpp
// 类模板
template<class T, class fromString = LexicalCast<std::string, T> , class toString = LexicalCast<T, std::string> >
class ConfigVar : public ConfigVarBase{
public:
typedef std::shared_ptr<ConfigVar> ptr;
// 当配置信息发送改变的时候,因为我们自定义的类型往往还只是string类型的结构,所以需要对应到实例内进行改变。 或者用于通知~
typedef std::function<void (const T& old_value, const T& new_value)> on_change_cb;
std::string toString() override{
try{
return ToString()(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(FromString()(val));
}catch(std::exception& e){
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::toString exception "
<< e.what() << " convert: string to " << getTypeName()
<< " - " << val;
}
return true;
}
const T getValue() const {return m_val;}
void setValue(const T& val) {
if(val == m_val){ // T 类型 需要 operator==
return;
}
for(auto& it : m_cbs){
it.second(m_val, val); // 调用相关的事件更改 通知 仿函数。
}
m_val = val;
}
void addListener(uint64_t key , on_change_cb fun){
m_cbs[key] = fun;
}
on_change_cb getListener(uint64_t key){
auto it = m_cbs.find(key);
return it == nullptr ? nullptr : it->second;
}
void delListener(uint64_t key){
m_cbs.
private:
T m_val;
// 需要一个map,管理不同多个的 事件通知函数,function仿函数没有办法区分,需要一个map。
std::map<uint64_t, on_change_cb> m_cbs;
};
- 进一步,对于自定义类型的设计。 a. 当配置信息发送改变的时候,因为我们自定义的类型往往还只是string类型的结构,所以需要对应到实例内进行改变。 见 ConfigVar 里的 m_cbs,当配置发送改变会依次调用处理函数。 b. 同样,我们需要对LexicalCast 进行偏特化。例如:
yaml
logs:
- name: root
level: info
appenders:
- type: StdoutLogAppender
pattern: "%d{%Y-%m-%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T[%m]%n"
- name: system
level: info
appenders:
- type: StdoutLogAppender
- type: FileLogAppender
file: /root/sylar-from-scratch/system.txt
日志系统 自定义类型 的转换
结合上面yaml的结构定义自定义类型
cpp
////////////////////////////////////////////////////////////////////////////////
// 从配置文件中加载日志配置
// 定义两个目标yaml的结构
struct LogAppenderDefine
{
int type = 0; // 1 File, 2 Stdout
std::string pattern;
std::string file;
bool operator==(const LogAppenderDefine& oth) const {
return type == oth.type &&
pattern == oth.pattern &&
file == oth.file;
}
};
struct LogDefine
{
std::string name;
LogLevel::Level level = LogLevel::UNKNOW;
std::vector<LogAppenderDefine> appenders;
bool operator==(const LogDefine& oth) const {
return name == oth.name && level == oth.level && appenders == oth.appenders;
}
// set 需要重载 <
bool operator < (const LogDefine& oth) const {
return name < oth.name;
}
};
template<>
class LexicalCast<std::string, LogDefine>{
public:
LogDefine operator() (const std::string& str){
YAML::Node node = YAML::Load(str);
LogDefine ld;
if(!node["name"].IsDefined()){
std::cout << "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>() : "UNKNOW");
if(node["appenders"].IsDefined()){ // 对应 YAML 数组结构
LogAppenderDefine lad;
for(size_t i = 0 ; i < node["appenders"].size() ; ++i){
auto it = node["appenders"][i];
if(!it["type"].IsDefined()){
std::cout << "log config error: appender type is null, " << it << std::endl;
throw std::logic_error("log config appender type is null");
continue;
}
std::string type_name = it["type"].as<std::string>();
if(type_name == "StdoutLogAppender"){
lad.type = 2;
if(it["pattern"].IsDefined()){
lad.pattern = it["pattern"].as<std::string>();
}
}else if(type_name == "FileLogAppender"){
lad.type = 1;
if(!it["file"].IsDefined()){
std::cout << "log config error: FilelogAppender file is null, " << it << std::endl;
continue;
}
lad.file = it["file"].as<std::string>();
if(it["pattern"].IsDefined()){
lad.pattern = it["pattern"].as<std::string>();
}
} else {
std::cout << "log appender config error: appender type is invalid, " << it << std::endl;
continue;
}
if(it["level"].IsDefined()){
// 强制要求 appender的level >= logger的level。
LogLevel::Level tem = LogLevel::FromString(it["level"].as<std::string>());
lad.level = std::max(tem, ld.level);
}else{
lad.level = ld.level; // 如果没有定义Appender的level,就使用logger的
}
ld.appenders.push_back(lad);
}
}
return ld;
}
};
template<>
class LexicalCast<LogDefine, std::string>{
public:
std::string operator()(const LogDefine& ld){
YAML::Node node;
node["name"] = ld.name;
node["level"] = LogLevel::ToString(ld.level);
for(auto& it : ld.appenders){
YAML::Node items;
if(it.type == 1){
items["type"] = "FileLogAppender";
items["file"] = it.file;
}else if(it.type == 2){
items["type"] = "StdoutLogAppender";
}
if(!it.pattern.empty()){
items["pattern"] = it.pattern;
}
items["level"] = LogLevel::ToString(it.level);
node["appenders"].push_back(items);
}
std::stringstream ss;
ss << node;
return ss.str();
}
};
Config
config 类 处理 yaml 配置文件的解析。 我们先需要一个 字符对应类型 的 map,确定 yaml 字符对应的转换 ConfigVar 特化子类
cpp
class Config{
public:
// 如果改成 string : ConfigVar<T> 就只能是 T 单一类型了。
// 所以 map 的键值不能为模板类,需要一个基类
typedef std::map<std::string, ConfigVarBase::ptr> ConvfigVarMap;
// 通过Lookup函数,对s_datas添加。
template<class T>
static typename ConfigVar<T>::ptr Lookup(const std::string& name,
const T& default_value, const std::string& description = ""){
auto it = s_datas.find(name);
if(it != s_datas.end()){
auto temp = std::dynamic_pointer_cast<ConfigVar<T> >(it->second);
if(temp){
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Lookup name = " << name << " exists";
return temp;
} else {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name = " << name << " exists but type is not "<< typeid(T).name()
<< " real_type = " << it->second->getTypeName() << " " << it->second->toString();
return nullptr;
}
}
// find_first_not_of 方法查找第一个不属于指定 字符集 的字符位置。
if(name.find_first_not_of("abcdefghijklmnopqrstuvwsyz._1234567890") != std::string::npos){
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lockup 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 Lockup(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);
}
static void LoadFromYaml(const YAML::Node& root);
static ConfigVarBase::ptr LookupBase(const std::string& name);
private:
static ConfigVarMap s_datas; // 约定大于配置,会事先约定好s_datas内容。
};
日志 配置 整合
yaml
logs:
- name: root
level: info
appenders:
- type: StdoutLogAppender
pattern: "%d{%Y-%m-%d %H:%M:%S}%T%t%T%F%T[%p]%T[%c]%T%f:%l%T[%m]%n"
- name: system
level: info
appenders:
- type: StdoutLogAppender
- type: FileLogAppender
file: /root/sylar-from-scratch/system.txt
cpp
// 自定义类型
// 详细 ~ 见上方
struct LogAppenderDefien{...};
struct LogDefine{...};
template<>
class LexicalCast<std::string, LogDefine>{...}
template<>
class LexicalCast<LogDefine, std::string>{...}
// 往Config的静态参数s_datas添加 键值对,"logs"对应转换的特化 ~
sylar::ConfigVar< std::set<LogDefine> >::ptr g_log_defines =
sylar::Config::Lookup("logs", std::set<LogDefine>(), "logs");
struct LogIniter{
LogIniter(){
/**
* 这个函数是,真正的,根据LogDefine,LogAppenderDefine的结构体,创建/修改/删除 对应的实例。
*
* 添加仿函数,在导入yaml或者发现配置文件LogDefine结构发生更改,赋值的时候,调用这个函数。
*/
g_log_defines->addListener(0x3f3f3f3f, [](const std::set<LogDefine>& old_log, const std::set<LogDefine>& new_log){
// 新增
// 修改
// 删除
for(auto& i : new_log){
auto it = old_log.find(i); // set<T> 按照 operator< 查找,那么也就是按照LogDefine name排序及查找
Logger::ptr logger;
if(it == old_log.end()){
// 需要新增
logger = SYLAR_LOG_NAME(i.name); // #define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger(#name)
}else {
if(!(i == *it)){ // name相同,但存在level或appender不同。
logger = SYLAR_LOG_NAME(i.name);
} else {
continue;
}
}
logger->setLevel(i.level);
logger->clearAppender();
//往logger里添加appender
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)));
} else {
ap->setFormatter(LogFormatter::ptr(new LogFormatter));
}
logger->addAppender(ap);
}
}
// 以配置文件为主,如果程序里定义了配置文件中未定义的logger,那么把程序里定义的logger设置成无效
// 新的没,旧的有。需要删除
for(auto &i : old_log){
auto it = new_log.find(i);
if(it == new_log.end()){
auto logger = SYLAR_LOG_NAME(i.name);
logger->setLevel(LogLevel::NOTEST); // logger的级别足够大,就不会输出事件。
logger->clearAppender();
}
}
});
}
};
//在main函数之前注册配置更改的回调函数
//用于在更新配置时将log相关的配置加载到Config
static LogIniter __log_init;
知识点
- 模板类的定义 放在 头文件。这是因为模板类的实例化需要编译器在编译时知道完整的模板定义。
- 抽象类的设计 ~
- 模板类的灵活使用 ~ 只可意会 哎 ~