[source code reading] - Sylar server framework: configuration module

Configuration module overview

   in the design of sylar's configuration module, the idea that convention is better than configuration is adopted, and its conventional use method is as follows:

sylar::ConfigVar<int>::ptr g_int_value_config =
    sylar::Config::Lookup("system.port", (int)8080, "system port");
// 8090 of type int is not defined in system.port, which can be passed through G_ int_ value_ Config - > getvalue() gets the current parameter value

  "agreement is better than configuration", also known as programming by agreement, is a software design paradigm, which aims to reduce the number of decisions that software developers need to make and obtain simple benefits without failure.
  in essence, developers only need to specify the non-conforming parts of the application. For example, if there is a class named sales in the model, the corresponding table in the database will be named sales by default. Only when you deviate from this Convention, such as naming the table "products_sole", do you need to write the configuration about this name.

Configure module related classes

 * @brief Base class of configuration variable
/**
 * @brief Base class of configuration variable
 */
class ConfigVarBase {};

/**
 * @brief Type conversion template class (F source type, T target type)
 */
template<class F, class T>
class LexicalCast {};

/**
 * @brief Configure parameter template subclasses and save parameter values of corresponding types
 * @details T Specific type of parameter
 *          FromStr Convert from std::string to T-type functor
 *          ToStr A functor that converts from T to std::string
 *          std::string String in YAML format
 */
template<class T, class FromStr = LexicalCast<std::string, T>
                ,class ToStr = LexicalCast<T, std::string> >
class ConfigVar : public ConfigVarBase {};

/**
 * @brief ConfigVar Management class of
 * @details Provides a convenient way to create / access ConfigVar
 */
class Config {};
  • ConfigVarBase: the base class of configuration variables, which is mainly virtual methods. It defines the common members and methods of configuration items. The specific experiments are implemented by the specific subclasses of the base class. The key points are toString() method and fromString() method, which are respectively responsible for converting the configuration information into a string and parsing the configuration from the string.
  • ConfigVar: configuration parameter template subclass, which is a template class. It has three template parameters: configuration item type template T, imitation function FromStr and imitation function Tostr. On the basis of ConfigVarBase, ConfigVar class adds AddListener method and delListener method to add or delete configuration change callback functions.
  • Config: the management class of ConfigVar, which provides convenient methods to create and access ConfigVar. Its main method does not include the Lookup method. You can query the configuration item according to the name of the configuration. If the corresponding configuration is not found during the query, create a new configuration item. It also has the methods of reading the configuration from the configuration file and traversing all configuration items. Among them, the methods in config are static methods to ensure that there is only one global instance.

YAML profile

  YAML is a concise non markup language. YAML takes data as the center, uses blank, indent and branch to organize data, so as to make the representation more concise and easy to read. In fact, for example:

# yaml test sample
# NULL or NULL is a keyword and cannot be written

# name
# character string
name: conf file

# edition
# If you press floating point, 2.0 will be converted to 2
# If you press the string, leave it as it is
version: 2.0

# Boolean class, converted to 1 or 0
need: true

# time
time: 2020-10-03T09:21:13

empty: nul

# object
# Double quotes will escape \ n, which will wrap the line
my:
  name: late \n lee
  name1: "late \n lee"
  age: 99
  
# block
text: |
  hello
  world!

# array
fruit:
  - apple
  - apple1
  - apple2
  - apple3
  - apple4
  - apple5

# Multilevel array
multi:
  sta:
    - 110 210 ddd 99
    - 133 135 1 2 1588 1509
    - 310-410
    - 333-444

   in C + +, YAML-cpp open source library can be used to read and write YAML files, and its configuration can be customized. For configuring YAML CPP with VScode on windows platform, please refer to a previous record: Configuring yaml CPP with VSCode on Windows platform , relevant tests can also be found above.

Partial specialization of type conversion

   in the configuration module of sylar, what needs to be added later based on your own needs is the partial specialization implementation of the type conversion function, which realizes the mutual conversion of basic types and Yaml strings. Partial specialization in C + + is between the main version template and the full specialization of the template. It only clearly defines some types rather than uniqueness of the template. Note that function templates cannot be partial specialization.

Integration of configuration module and log module

sylar::ConfigVar<std::set<LogDefine> >::ptr g_log_defines =
    sylar::Config::Lookup("logs", std::set<LogDefine>(), "logs config");

//Define logdefine, logappenderdefine, partial specialization lexicast,
//Implement log configuration resolution
struct LogIniter {
    LogIniter() {
        g_log_defines->addListener([](const std::set<LogDefine>& old_value,
                    const std::set<LogDefine>& new_value){
            SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "on_logger_conf_changed";
            for(auto& i : new_value) {
                auto it = old_value.find(i);
                sylar::Logger::ptr logger;
                if(it == old_value.end()) {
                    //New logger
                    logger = SYLAR_LOG_NAME(i.name);
                } else {
                    if(!(i == *it)) {
                        //Modified logger
                        logger = SYLAR_LOG_NAME(i.name);
                    } else {
                        continue;
                    }
                }
                logger->setLevel(i.level);
                //std::cout << "** " << i.name << " level=" << i.level
                //<< "  " << logger << std::endl;
                if(!i.formatter.empty()) {
                    logger->setFormatter(i.formatter);
                }

                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) {
                        ap.reset(new StdoutLogAppender);
                    }
                    ap->setLevel(a.level);
                    if(!a.formatter.empty()) {
                        LogFormatter::ptr fmt(new LogFormatter(a.formatter));
                        if(!fmt->isError()) {
                            ap->setFormatter(fmt);
                        } else {
                            std::cout << "log.name=" << i.name << " appender type=" << a.type
                                      << " formatter=" << a.formatter << " is invalid" << std::endl;
                        }
                    }
                    logger->addAppender(ap);
                }
            }

            for(auto& i : old_value) {
                auto it = new_value.find(i);
                if(it == new_value.end()) {
                    //Delete logger
                    auto logger = SYLAR_LOG_NAME(i.name);
                    logger->setLevel((LogLevel::Level)0);
                    logger->clearAppenders();
                }
            }
        });
    }
};

// Define a LogIniter class that does not instantiate a global object.
static LogIniter __log_init;
logs: 
    - name: root
      level: (debug,info,warn,error,fatal)
      formatter: '%d%T%p%T%t%m%n'
      appender:
        - type: (StdoutLogAppender, FileLogAppender)
          level:(debug,...)
          file: /logs/xxx.log
    sylar::Logger g_logger = sylar::LoggerMgr::GetInstance()->getLogger(name);
    SYLAR_LOG_INFO(g_logger) << "xxxx log";
void test_log() {
    static sylar::Logger::ptr system_log = SYLAR_LOG_NAME("system");
    SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
    std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
    YAML::Node root = YAML::LoadFile("/home/sylar/workspace/sylar/bin/conf/log.yml");
    sylar::Config::LoadFromYaml(root);
    std::cout << "=============" << std::endl;
    std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
    std::cout << "=============" << std::endl;
    std::cout << root << std::endl;
    SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;

    system_log->setFormatter("%d - %m%n");
    SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
}

Further understanding is needed

  • As for the configuration module, I don't think there is a lot of code and it's not difficult to understand, but I don't know its specific purpose. Although it is useful in subsequent modules, I'm still confused about how to load the default configuration during system initialization and how to unify a series of initialization parameters after the configuration file is modified, Maybe I haven't had much contact with this systematic thing. If the boss understands it, I can explain it. Thank you~
  • As for the integration of the log module and the configuration module, in fact, I still feel a little confused in the integration of the two modules. After looking at the test, I should read the content of the configuration file through LoadFromYaml, and then configure the log system with name, level, appenders, etc. here, I will check the process step by step through debugging.
  • As for the configuration module, I mainly focus on reading without trying to modify the content. Here I mainly read yaml configuration files, and later I try to read other files, but my ability may not be enough and I need to learn more~

Tags: C++ Linux Back-end server

Posted on Tue, 23 Nov 2021 03:00:48 -0500 by davidjmorin