【sylar-webserver】2 配置系统

配置系统

ConfigVarBase 和 ConfigVar

每个配置项,由多个 key,value 和 description 组成。这三个可以由 string 保存,每个 value 对应着多个类型(可以是 int,float,bool,自定义类型...)。 那么,基类 ConfigVarBase 声明 key 和 description,以及纯虚函数的析构,toString,fromString。(toString, fomString 分别对应着 value 从 string -> T 反序列化,以及 T-> string 序列化)

  1. 对应 基础类型 的 设计,可以直接使用 boost 库里的 boost::lexical_cast(F); 从 F 类型转换到 T 类型。为了方便下一步的设计,设计模板类LexicalCast。
  2. 对于 满足 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; 	
};
  1. 进一步,对于自定义类型的设计。 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;

知识点

  1. 模板类的定义 放在 头文件。这是因为模板类的实例化需要编译器在编译时知道完整的模板定义。
  2. 抽象类的设计 ~
  3. 模板类的灵活使用 ~ 只可意会 哎 ~
相关推荐
又过一个秋4 小时前
【sylar-webserver】1 日志系统
后端
成都第一深情IZZO4 小时前
工厂模式(使用Spring Bean)和 策略工厂模式 的区别
后端
该用户已不存在5 小时前
写了这么多年Java,这几个神仙技巧你用过吗?
java·后端
大雨淅淅5 小时前
【编程语言】Rust 入门
开发语言·后端·rust
桃花键神5 小时前
【送书福利-第四十四期】《 深入Rust标准库》
开发语言·后端·rust
像风一样自由20205 小时前
使用Rust构建高性能文件搜索工具
开发语言·后端·rust
xuejianxinokok5 小时前
io_uring 快吗? Postgres 17 与 18 的基准测试
数据库·后端·postgresql
DokiDoki之父6 小时前
SpringMVC—REST风格 & Restful入门案例 & 拦截器简介 & 拦截器入门案例 & 拦截器参数 & 拦截器链配置
后端·restful
JohnYan6 小时前
安全密钥(Security Key)和认证技术相关词汇表
后端·安全·设计模式