likes
comments
collection
share

sylar-from-scratch----配置模块config

作者站长头像
站长
· 阅读数 35

配置模块概述

1、配置变量基类ConfigVarBase

  • 构造函数 参数:配置参数名称name,配置参数描述description; 功能:构造配置参数的名称与其描述,使用std::transform函数将配置参数名称转为小写。
  • 三个纯虚函数 转成字符串toString(); 从字符串初始化值fromString(); 返回配置参数的类型名称getTypeName()

类型转换模板类(F 源类型, T目标类型)

重载operator()参数为源类型值,使用boost库中的类型转换模板将其转换为目标类型值。

template <class F, class T>
class LexicalCast {
public:
    /**
     * @brief 类型转换
     * @param[in] v 源类型值
     * @return 返回v转换后的目标类型
     * @exception 当类型不可转换时抛出异常
     */
    T operator()(const F &v) {//boost库中的类型转换模板,将元类型的值转换为目标类型的值,并返回结果,若不转换则抛出异常
        return boost::lexical_cast<T>(v);
    }
};

以该模板实现YAML string与vector、list、set、unordered_set、map、unordered_map间的相互转化。如下所示为unordered_set的相互转换:

template <class T>
class LexicalCast<std::string, std::unordered_set<T>> {
public:
    std::unordered_set<T> operator()(const std::string &v) {
        YAML::Node node = YAML::Load(v);
        typename std::unordered_set<T> vec;
        std::stringstream ss;
        for (size_t i = 0; i < node.size(); ++i) {
            ss.str("");
            ss << node[i];
            vec.insert(LexicalCast<std::string, T>()(ss.str()));
        }
        return vec;
    }
};
template <class T>
class LexicalCast<std::unordered_set<T>, std::string> {
public:
    std::string operator()(const std::unordered_set<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();
    }
};

2、配置参数类ConfigVar

配置参数模板子类,保存对应类型的参数值。继承自ConfigVarBase类,为一个模板类,模板参数为参数的具体类型T、使用类型转换模板类将string转换为T类型FromStr、将T类型转换为YAML格式字符串类型的ToStr,后两个参数为仿函数。

  • 构造函数 参数:配置参数名name,配置参数描述descriptions,参数的默认值default_value。 功能:初始化该类
  • 获取当前参数值getValue()

设置当前参数值setValue()

若当前参数值不发生与目标参数值相同则直接返回,否则通知对应的注册回调函数。

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

回调函数的操作

变更回调函数组以map<uint64_t, on_change_cb> m_cbs结果存储,key为回调函数的唯一id,其操作有添加、删除、获取、清理回调函数addListener()\delListener()\getListener()\clearListener()。

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

实现父类ConfigVarBase的三个虚函数的重写

  • toString()函数 首先读锁保护代码资源,然后使用模板参数中的Tostr将参数值转换为YAML string类型返回;若转换失败则捕捉到异常将其记录在日志中,最后返回一个空格。
 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 "";
    }
  • fromString()函数 setValue函数将YAML string转换为T类型的参数值通过setValue函数设置,转换失败时同样将错误信息记录在日志。
 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;
    }
  • getTypeName()函数 std::string getTypeName() const override { return TypeToName<T>(); }

3、管理类Config

ConfigVar的管理类,提供便捷的方法创建或访问ConfigVar;全部成员变量和方法都是static类型,保证了全局只有一个实例。

lookup函数

  • 函数重载---获取并创建对应参数名:根据配置名称查询配置项,如果名称存在且类型匹配直接返回,若名称存在但类型不匹配就记录错误信息,若参数名中存在非法字符,则记录错误信息并抛出无效参数异常;若参数名不存在,则创建配置参数并使用default_value赋值,保证了配置模块定义即可用的特性。
 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()) {
            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;
            }
        }
         //参数名包含非法字符,记录错误信息并抛出无效参数异常
        if (name.find_first_not_of("abcdefghikjlmnopqrstuvwxyz._012345678") != std::string::npos) {
            SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid " << name;
            throw std::invalid_argument(name);
        }
        //创建参数名,使用默认值与描述进行赋值,返回该参数名
        typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, default_value, description));
        GetDatas()[name] = v;
        return v;
    }
  • 函数重载---查找配置参数名返回参数名对应的配置参数:查找name的配置参数,若配置参数名不存在,则返回空指针,若存在则返回其配置参数。
 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);
    }
  • LoadFromYaml()YAML::Node初始化配置模块
  • LoadFromConfDir()加载path文件夹中的配置文件
  • LookupVarBase()查找配置参数,返回配置参数基类
  • Visit()遍历配置模块中所有的配置项

日志模块与配置模块

1、日志器配置结构体定义LogDefine

 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 == appenders;
    }

    bool operator<(const LogDefine &oth) const {//重载小于
        return name < oth.name;
    }

    bool isValid() const {//配置项是否有效
        return !name.empty();
    }
};   

2、日志输出器配置结构体定义LogAppenderDefine

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

3、YAML:string转化为LogDefine对象

实现将字符串表示的日志配置信息转化为LogDefine对象,方便日志配置的解析和使用;从YAML中逐步获取日志器名称、日志级别和日志输出器保存在LogDefine类型的对象中并返回,输出器这里只有两种StdoutLogAppender和FileLogAppender;若读取过程中出现错误则打印相应信息

template<>//类型模板
class LexicalCast<std::string, LogDefine> {
public:
    LogDefine operator()(const std::string &v) {
        YAML::Node n = YAML::Load(v);//这里的string是YAML:string
        LogDefine ld;
        if(!n["name"].IsDefined()) {
            std::cout << "log config error: name is null, " << n << std::endl;
            throw std::logic_error("log config name is null");
        }
        ld.name = n["name"].as<std::string>();
        ld.level = LogLevel::FromString(n["level"].IsDefined() ? n["level"].as<std::string>() : "");

        if(n["appenders"].IsDefined()) {
            for(size_t i = 0; i < n["appenders"].size(); i++) {
                auto a = n["appenders"][i];
                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;
                if(type == "FileLogAppender") {
                    lad.type = 1; 
                    if(!a["file"].IsDefined()) {
                        std::cout << "log appender config error: file appender file is null, " << a << std::endl;
                        continue;
                    }
                    lad.file = a["file"].as<std::string>();
                    if(a["pattern"].IsDefined()) {
                        lad.pattern = a["pattern"].as<std::string>();
                    }
                } else if(type == "StdoutLogAppender") {
                    lad.type = 2;
                    if(a["pattern"].IsDefined()) {
                        lad.pattern = a["pattern"].as<std::string>();
                    }
                } else {
                    std::cout << "log appender config error: appender type is invalid, " << a << std::endl;
                    continue;
                }
                ld.appenders.push_back(lad);
            }
        } // end for
        return ld;
    }
};    

4、LogDefine转换为YAML:string类型

实现将LogDefine对象转换为字符串表示形式的功能,方便在需要时将日志配置信息序列化为字符串,比如保存到文件或网络传输等应用场景。

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、日志系统的动态配置管理

  • 日志项配置项的构建:在log.cpp文件中对于日志器配置项的构建,配置项名称为“log”,配置参数为定义的日志器,描述为“logs config”。 sylar::ConfigVar<std::set<LogDefine>>::ptr g_log_defines = sylar::Config::Lookup("logs", std::set<LogDefine>(), "logs config"); 动态配置使得日志输出器的配置可以在运行时进行调整,而不需要重新编译和部署程序,可以更灵活地适应不同的运行环境和需求。
  struct LogIniter {
    LogIniter() {
        //增加回调函数,参数为lambda的匿名函数,匿名函数参数为新日志配置项与旧配置项
        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;              
                    // 新增logger
                    logger = SYLAR_LOG_NAME(i.name);
                } else {
                    if(!(i == *it)) {
                        // 修改的logger
                        logger == SYLAR_LOG_NAME(i.name);
                    } else {
                        continue;
                    }
                }
                logger->setLevel(i.level);
                logger->clearAppenders();
                for(auto &a : i.appenders) {//清空已有的输出器,添加新的输出器
                    sylar::LogAppender::ptr ap;
                    if(a.type == 1) {
                        ap.reset(new FileLogAppender(a.file));
                    } else if(a.type == 2) {
                        // 如果以daemon方式运行,则不需要创建终端appender
                        if(!sylar::EnvMgr::GetInstance()->has("d")) {
                            ap.reset(new StdoutLogAppender);
                        } else {
                            continue;
                        }
                    }
                    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_value) {
                auto it = new_value.find(i);
                if(it == new_value.end()) {
                    auto logger = SYLAR_LOG_NAME(i.name);
                    logger->setLevel(LogLevel::NOTSET);
                    logger->clearAppenders();
                }
            }
        });    
};  

在main函数之前注册配置更改的回调函数,使得在更新配置的时候将log相关的配置加载到config中; static LogIniter __log_init;


基础知识

std::function

c++11新特性,包含头文件#include,是一种通用、多态的函数封装的类模板,对可调用对象进行封装,函数使用方法为 std::function<返回值类型(参数类型列表)> 包装器名称=可调用对象; 其中可调用对象可以为函数指针、仿函数、可被转换为函数指针的类对象、类成员函数指针、类成员指针。

回调函数

回调函数是一个通过函数指针调用的函数,如果把函数的指针(地址)作为参数传递给另一个参数,当这个指针被用来调用其所指的函数时,该函数为回调函数,即以参数的形式传递给其它代码的可执行代码。

  • 使用回调函数的目的:解耦 回调函数与普通函数调用的区别在于在回调中,主程序把回调函数像参数一样传入库函数,这样就可以通过改变传入库函数的参数实现不同的功能,在此过程中不需要改变库函数的实现。这时主函数和回调函数在同一层,而库函数在另一层,库函数不可见,也无法修改库函数中的普通函数调用,因此无法实现相应功能,只能使用回调函数。

  • 与普通函数的区别 一般的函数我们自己编写的函数会在自己的程序内部调用,也就是说函数的编写方和调用方都是我们自己;而回调函数不是,函数的编写方式我们自己,但调用方需要使用第三方库,即调用第三方库中的函数,将回调函数传递给第三方库,第三方库中的函数调用我们编写的回调函数。

  • 同步回调与异步回调 回调函数在调用的那一刻立即被执行为同步回调,如果在之后晚点的某个时间再执行,则为异步回调。

  • 回调函数调用的时间节点 回调函数只在某些特定的节点被调用,如接收到网络数据、文件读取完成等事件,即回调函数就是来处理event的,从本质上来说回调函数是一个event handler,适用于事件驱动编程event-driven。

    www.runoob.com/w3cnote/c-c…

转载自:https://juejin.cn/post/7374677514536370203
评论
请登录