sylar-from-scratch----配置模块config
配置模块概述
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。
转载自:https://juejin.cn/post/7374677514536370203