实现的功能
通过文件配置的方式实现对日志系统的配置,比如说日志的名称,日志的格式,日志的级别,日志有哪几个appender,appender的级别、类型、格式等。配置变更事件,当文件中配置发生改变时,更新日志系统配置。
如何实现?
首先,日志配置的yaml文件如下:
logs:
- name : root
level : info
formatter: '%d%T%p%T%t%T%m%n'
appenders :
- type : FileLogAppender
file : log.txt
- type : StdoutLogAppender
- name : system
level : debug
formatter: '%d%T%t%T%m%n'
appenders :
- type : FileLogAppender
file : log.txt
formatter: '%p%T%t%n'
- type : StdoutLogAppender
之前学配置系统时,已经知道了将yaml文件加载进来,是经过一系列解析存到了Config类中的s_datas变量中,存储格式为map,即通过键获得值。
首先,将日志的配置加载到Config中,我们要自定义日志类配置信息的类以及偏特化方法,这样才能加载进来
yaml文件就是通过缩进的形式来表示层级关系,其实就是表示一个对象,既然我们要把yaml文件加载到系统中,我们肯定需要按照yaml文件内的结构关系,封装出来一个类 or 结构体。
需要增加的结构体、类、函数等
对于上面的yaml文件,我们需要封装如下的两个类:
LogDefine类(或者结构体),包含属性:name、level、formatter、appenders(列表类型)
LogAppenderDefine类(或者结构体),包含属性:type、level、formatter、file
都需要重载运算符==(setValue中需要用到,判断两个对象是否相等)。
具体实现如下:
struct LogAppenderDefine
{
// 具体的yaml文件中appender定义
int type; // 1 File,2 Stdout
LogLevel::Level level = LogLevel::UNKNOW;
std::string formatter;
std::string file;
// 重载等于运算符,ConfigVar->setValue会用到
bool operator==(const LogAppenderDefine &oth) const
{
return type == oth.type &&
level == oth.level &&
formatter == oth.formatter &&
file == oth.file;
}
};
struct LogDefine
{
// 与yaml文件中定义的一致,对应起来,这类似于自定义类Person
std::string name;
LogLevel::Level level = LogLevel::UNKNOW;
std::string formatter;
std::vector<LogAppenderDefine> appenders;
// 重载等于运算符,ConfigVar->setValue会用到
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;
}
bool isValid() const
{
return !name.empty();
}
};
实现与string相互转化的偏特化
为了使得LogDefine类与string进行相互转换,需要实现偏特化,代码如下:
/**
* string -> LogDefine
*/
template <>
class LexicalCast<std::string, LogDefine>
{
public:
LogDefine operator()(const std::string &v)
{
YAML::Node n = YAML::Load(v); // 将字符串使用yaml-cpp加载
LogDefine ld;
if (!n["name"].IsDefined())
{
// 不存在name这个key
std::cout << "log config error: name is null, " << n
<< std::endl;
throw std::logic_error("log config name is null");
}
// 设置name
ld.name = n["name"].as<std::string>();
// 设置level
ld.level = LogLevel::FromString(n["level"].IsDefined() ? n["level"].as<std::string>() : "");
// 设置格式
if (n["formatter"].IsDefined())
{
ld.formatter = n["formatter"].as<std::string>();
}
// 设置appender
if (n["formatter"].IsDefined())
{
// 遍历YAML树中n["appenders"]的每一个节点
for (size_t x = 0; x < n["appenders"].size(); x++)
{
auto a = n["appenders"][x]; // 取出节点
if (!a["type"].IsDefined())
{
// appender 没有type,则出错
std::cout << "log 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 config error: fileappender file is null, " << a
<< std::endl;
continue;
}
lad.file = a["file"].as<std::string>();
if (a["formatter"].IsDefined())
{
lad.formatter = a["formatter"].as<std::string>();
}
}
else if (type == "StdoutLogAppender")
{
lad.type = 2;
if (a["formatter"].IsDefined())
{
lad.formatter = a["formatter"].as<std::string>();
}
}
else
{
std::cout << "log config error: appender type is invalid, " << a
<< std::endl;
continue;
}
// 每找到一个就push_back到vector中
ld.appenders.push_back(lad);
}
}
return ld;
}
};
/**
* LogDefine -> string
*/
template <>
class LexicalCast<LogDefine, std::string>
{
public:
std::string operator()(const LogDefine &i)
{
// 将LogDefine对象中的值依次设置到YAML::Node中
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)
{
// 遍历每个appender,需要根据类型设置
YAML::Node na;
if (a.type == 1)
{
na["type"] = "FileLogAppender";
na["file"] = a.file;
}
else if (a.type == 2)
{
na["type"] = "StdoutLogAppender";
}
// 每个单独的appender也要单独设置level和formatter
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文件内容的类,和该类与string相互转换的偏特化类。那就可以调用LoadFromYaml方法,将变量解析并存放到Config类中s_datas中。
// 使用yaml-cpp加载yaml文件
YAML::Node root = YAML::LoadFile("/root/c_plus_plus_project/sylar/bin/conf/logs.yaml");
// 加载yaml节点到Config中
sylar::Config::LoadFromYaml(root);
Config类中变量赋值给日志系统
那么我们如何把Config中的这些变量配置赋给日志系统呢?
这就用到了之前写的“配置变更事件”,自定义回调函数,在Config中setValue方法中,检测到值发生变化后,调用回调函数,将新值加载到日志管理类LoggerMgr中(里面包含很多个日志类)。
注意:在回调函数中,会使用LoggerMgr类新建一个日志类(如果不存在),或者重新赋值(如果存在),LoggerMgr为单例模式,全局只有一个实例,来进行日志系统的配置。
我们之前的配置记为old_value,从yaml新加载的配置记为new_value。将old_value赋给new_value需要做一些判断
new_value的值不存在于old_value则需要新建一个。
new_value的值存在于old_value,但两个值不相等,则需要进行更新。
new_value的值存在于old_value,两个值相等,不需要做任何操作。
new_value中没有old_value的一些值,需要将这些值删掉。
剩下的就是针对具体的配置内容,实现配置。
总结:配置系统与任何系统整合
- 增加必要的结构体or类,用来存放yaml文件中的配置
- 实现与string相互转换的偏特化
- 利用回调函数,书写赋值逻辑,将值赋给目标系统
序列化
刚刚所讲的是从yaml文件中加载到系统中,属于反序列化。
下面说一下如何实现反序列化,即讲对象转为yaml,存到yaml文件中。
简单说就是在类中增加一个toYamlString()方法,在方法内部,定义一个YAML::Node节点,将要序列化内容全都以键值对的形式赋给Node节点,然后再将Node节点转为字符串,存到yaml文件中。
std::string LoggerManager::toYamlString()
{
// MutexType::Lock lock(m_mutex);
YAML::Node node;
// 遍历所有的logger,调用toYamlString方法
for (auto &i : m_loggers)
{
node.push_back(YAML::Load(i.second->toYamlString()));
}
std::stringstream ss;
ss << node;
return ss.str();
}
std::string Logger::toYamlString()
{
// MutexType::Lock lock(m_mutex);
YAML::Node node;
// name
node["name"] = m_name;
// level
if (m_level != LogLevel::UNKNOW)
{
node["level"] = LogLevel::ToString(m_level);
}
// formatter
if (m_formatter)
{
node["formatter"] = m_formatter->getPattern();
}
// appenders
for (auto &i : m_appenders)
{
// 调用每个appender的toYamlString方法
node["appenders"].push_back(YAML::Load(i->toYamlString()));
}
std::stringstream ss;
ss << node;
return ss.str();
}
std::string StdoutLogAppender::toYamlString()
{
// MutexType::Lock lock(m_mutex);
YAML::Node node;
node["type"] = "StdoutLogAppender";
if (m_level != LogLevel::UNKNOW)
{
node["level"] = LogLevel::ToString(m_level);
}
if (m_hasFormatter && m_formatter)
{
node["formatter"] = m_formatter->getPattern();
}
std::stringstream ss;
ss << node;
return ss.str();
}
序列化保存到文件的代码如下:
std::string yaml_output_file = "/root/c_plus_plus_project/sylar/bin/conf/logs_output.yaml";
std::ofstream m_filestream;
m_filestream.open(yaml_output_file);
// 序列化到文件
m_filestream << sylar::LoggerMgr::GetInstance()->toYamlString();
m_filestream.close();
序列化后,logs_output.yaml文件内容如下:
- name: root
level: INFO
formatter: "%d%T%p%T%t%T%m%n"
appenders:
- type: FileLogAppender
file: log.txt
- type: StdoutLogAppender
- name: system
level: DEBUG
formatter: "%d%T%t%T%m%n"
appenders:
- type: FileLogAppender
file: log.txt
formatter: "%p%T%t%n"
- type: StdoutLogAppender