sylar源码阅读笔记-配置系统与日志系统整合-序列化

实现的功能

通过文件配置的方式实现对日志系统的配置,比如说日志的名称,日志的格式,日志的级别,日志有哪几个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的一些值,需要将这些值删掉。

剩下的就是针对具体的配置内容,实现配置。

总结:配置系统与任何系统整合
  1. 增加必要的结构体or类,用来存放yaml文件中的配置
  2. 实现与string相互转换的偏特化
  3. 利用回调函数,书写赋值逻辑,将值赋给目标系统
序列化

刚刚所讲的是从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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值