log4cpp源码阅读:PropertyConfigurator解析

1059 篇文章 275 订阅

PropertyConfigurator可以帮助我们从一个配置文件中来自动创建好Category对象、Appender对象、Layout对象,并且自动设置好它们的对应关系。然后我们通过Category::getInstance方法就获取配置文件所设置的category名称,直接进行日志操作。省去了我们自己构造Appender对象,自己构造Layout。非常方便。

Properties

这个类似就是把形如:name = ffxage = 24student_info = ${name}, ${age}这些信息以键值对的方式保持到内部的map。

// 需要特别注意,他是map<string, string>的子类
class Properties : public std::map<std::string, std::string> {
public:
    ...

    // 将内容的元素以 key = value形式保存到out中
    virtual void save(std::ostream& out);

    // 从map中获取指定key的值,如果指定key不存在,则返回defaultValue
    virtual int getInt(const std::string& property, int defaultValue);
    virtual bool getBool(const std::string& property, bool defaultValue);
    virtual std::string getString(const std::string& property,
    const char* defaultValue);
    ...
};

// 将内容的元素以 key = value\n形式保存到out中
void Properties::save(std::ostream& out) {
    for(const_iterator i = begin(); i != end(); ++i) {
        out << (*i).first << "=" << (*i).second << std::endl;
    }
}

// 从当前map中获取指定的以property为key,并且将他们转换成合适类型的值,
// 如果Key不存在,则返回defaultValue
int Properties::getInt(const std::string& property, int defaultValue) {
    const_iterator key = find(property);
    return (key == end()) ? defaultValue : std::atoi((*key).second.c_str());
}

bool Properties::getBool(const std::string& property, bool defaultValue) {
    const_iterator key = find(property);
    return (key == end()) ? defaultValue : ((*key).second == "true");
}

std::string Properties::getString(const std::string& property, 
                                  const char* defaultValue) {
    const_iterator key = find(property);
    return (key == end()) ? std::string(defaultValue) : (*key).second;
}

通过save方法我们可以大致猜测到,Properties可以将内部的key-value以key=value形式保存到输出流,并且可以获得执行属性的指定值。

除了上面的方法外,它还有一个load方法,用于从指定格式的配置文件中加载信息,加载的文件的格式和保存的最终文件格式内容是相同的:

class Properties : public std::map<std::string, std::string> {
public:
    virtual void load(std::istream& in);
protected:
    virtual void _substituteVariables(std::string& value);
};
void Properties::load(std::istream& in) {
        clear();  // 先清空当前map容器中的所有内容

        std::string fullLine, command;
        std::string leftSide, rightSide;
        char line[256];
        std::string::size_type length;
        bool partiallyRead(false);	// fix for bug#137, for strings longer than 256 chars

        while (in) {
        	// 读取完整的一行
        	if (in.getline(line, 256) || !in.bad()) { 
        		if (partiallyRead)
        			fullLine.append(line);
        		else
        			fullLine = line;

				//当一行文本超过256个字符的时候,in.getLine,会导致一个ios::failbit被设置,这样下次就无法继续读取内容,并且读取的当前行的内容也是不完整的
        		partiallyRead = (in.fail() && !in.bad());
                  // 对于这种情况,要手动将failbit标志清除,然后设置读取当前行剩余的内容
        		if (partiallyRead && !in.eof()) {
        			in.clear(in.rdstate() & ~std::ios::failbit);
        			continue; // to get full line
        		}
        	} else {
       			break;
        	}
            // fullLine保存了当前行的完整内容
			//下面的逻辑就是将注释前面的有效内容设置到command中
            length = fullLine.find('#');
            if (length == std::string::npos) {
                command = fullLine;
             //length > 0是为了避免整行都是注释内容,只有当注释位于行尾的时候 才会将#前面的内容当做命令
            } else if (length > 0) { 
                command = fullLine.substr(0, length);
            } else {
                continue;
            }

          // 因为log4cpp规定了配置文件的格式必须是 左值 = 右值
       	 // 所以才有下面的逻辑来提取左右值
            length = command.find('=');
            if (length != std::string::npos) {
                leftSide = StringUtil::trim(command.substr(0, length));
                rightSide = StringUtil::trim(command.substr(length + 1, command.size() - length));
                // 替换右值中的形如${xxx}内容,为环境变量中的内容,或者是具体key的内容
           		 // 具体代码我们后面会进行介绍
                _substituteVariables(rightSide);
            } else {
                continue;
            }

            //下面主要是为了去除左值是形如log4j.category.categoryName或者是log4cpp.category.categoryName
        //这些情况
            length = leftSide.find('.');
            if (leftSide.substr(0, length) == "log4j" ||
                leftSide.substr(0, length) == "log4cpp"){
                leftSide = leftSide.substr(length + 1);
			}
               
            //将当前左值和右值保存到map容器中
            insert(value_type(leftSide, rightSide));
        }
    }
// 替换value中的形如${xx}为具体环境变量的值,或者是对应key的值
// value既是输入参数也是输出参数
void Properties::_substituteVariables(std::string& value) {
    std::string result; //用来保存最终的结果

    std::string::size_type left = 0;
    std::string::size_type right = value.find("${", left); //查找${起始位置

    // 没有找到,则说明value中没有要替换的变量,则直接返回
    if (right == std::string::npos) {
        return;
    }

    while(true) {
        result += value.substr(left, right - left); //将${左面的值添加到result中(就是普通文本)
        if (right == std::string::npos) {
            break;
        }

        left = right + 2;                           //left定位到${后面的位置
        right = value.find('}', left);              //right定位到}位置

        // 如果只有${没有},那么log4cpp会认为${后面的内容都是普通文本,然后结束循环
        if (right == std::string::npos) {
            result += value.substr(left - 2);       
            break; 
        } else { //否则{}中的内容位于[left,right)区块中
            const std::string key = value.substr(left, right - left); //获取大括号中的内容
            if (key == "${") {  //处理的是${${}的情况,log4cpp只会把${当做普通字符串来进行处理
                result += "${";
            } else {
                char* value = std::getenv(key.c_str()); //获取环境变量对应的值
                if (value) {
                    result += value;                    //如果值存在,则把对应的值添加到结果中
                } else {    //否则log4cpp尝试查看这个key是不是map的key
                    const_iterator it = find(key);
                    if (it == end()) {
                                                    //如果不是的,他会把这个${xx}当做一个空字符
                    } else {
                        result += (*it).second;     //是的话,则将对应的值添加到结果中
                    }
                }
            }
            left = right + 1;   //表示是}后面的位置,其实就是下一个普通文本的起始位置
        }

        right = value.find("${", left); //right表示下一个${的位置
    }

    value = result; //value既是输入参数又是输出参数
}

总结一下load内部干了什么事

  • 读取每一行的所有文本内容
  • 当这一行文本整行都是注释内容的话,则读取下一行,否则读取所有非注释内容到command中
  • 分离出左值和右值
  • 替换右值中的${xx}部分为对应环境变量的值
  • 将左值中不必要的log4j或者log4cpp前缀移除掉
  • 将左值内容和右值内容插入到this中(this是一个std::mapstd::string,std::string类型)

PropertyConfigurator

配置文件样例格式

我们首先来看一下这个类支持什么样的配置,不然在内部执行解析操作的时候,都不知道这个算法为什么这样写

log4j.rootCategory=DEBUG, rootAppender 
log4j.category.sub1=A1 
log4j.category.sub2=INFO 
log4j.category.sub1.sub2=ERROR, A2

log4j.appender.rootAppender=org.apache.log4j.ConsoleAppender log4j.appender.rootAppender.layout=org.apache.log4j.BasicLayout

log4j.appender.A1=org.apache.log4j.FileAppender log4j.appender.A1.fileName=A1.log log4j.appender.A1.layout=org.apache.log4j.BasicLayout

log4j.appender.A2=org.apache.log4j.ConsoleAppender log4j.appender.A2.layout=org.apache.log4j.PatternLayout log4j.appender.A2.layout.ConversionPattern=The message %m at time %d%n

实现

 class PropertyConfigurator {
  public:
        static void configure(const std::string& initFileName);
};

内部只有一个configure静态方法

void PropertyConfigurator::configure(const std::string& initFileName) throw (ConfigureFailure) {
    PropertyConfiguratorImpl configurator;  //真正实现此方法的是PropertyConfiguratorImpl,这儿应该可以算是桥接模式
    configurator.doConfigure(initFileName);
}

PropertyConfiguratorImpl

这个类内部真正实现了配置文件加载的具体过程

内部成员属性

class PropertyConfiguratorImpl {
public:
    typedef std::map<std::string, Appender*> AppenderMap;

    PropertyConfiguratorImpl();
    virtual ~PropertyConfiguratorImpl();
    ...
protected:
    Properties _properties;         //可以从配置文件中加载信息的一个map<string,string>
    AppenderMap _allAppenders;
};


  • _properties,可以从配置文件中加载信息到map<string,string>
  • _allAppenders则是一个map<string,Appender*>,key是appenerName, Appender*是对应的Appender对象
 	PropertyConfiguratorImpl::PropertyConfiguratorImpl() {
    }

    PropertyConfiguratorImpl::~PropertyConfiguratorImpl() {
    }

doConfigure

class PropertyConfiguratorImpl {
public:
    ...
    // 使用指定的文件名称来获取进行配置
    virtual void doConfigure(const std::string& initFileName) throw (ConfigureFailure);

    //使用指定的流来进行配置
    virtual void doConfigure(std::istream& in)  throw (ConfigureFailure);
protected:
    ...
};
// 内部利用文件输入流来读取此initFileName文件,然后调用使用输入流为参数的doConfigure重载函数
void PropertyConfiguratorImpl::doConfigure(const std::string& initFileName) {
        std::ifstream initFile(initFileName.c_str());

        if (!initFile) {
            throw ConfigureFailure(std::string("File ") + initFileName + " does not exist");
        }

        doConfigure(initFile);
}

// 内部应该是真正执行解析流的地方
void PropertyConfiguratorImpl::doConfigure(std::istream& in) {
        _properties.load(in);// 真正执行文件的操作是发生在这儿
                            // 执行过此方法,_properties内部就包含了配置文件中所有有用的信息


        instantiateAllAppenders();  //我们暂且先不看他的具体实现,他的功能就是利用_properties信息来实例化所有appender对象


        std::vector<std::string> catList;
        getCategories(catList);  //内部实现是获取所有category的名称到catList中,我们暂且也不看

        // 配置每一个category对象
        for(std::vector<std::string>::const_iterator iter = catList.begin();
            iter != catList.end(); ++iter) {
            configureCategory(*iter);  //利用此方法来配置category,传入的参数是配置名称
        }
    }

我们会发现,PropertyConfigurator::configure,内部调用的是PropertyConfiguratorImpl::doConfigure(string &initFileName)方法,而这个方法 又调用了PropertyConfiguratorImpl::doConfigure(istream&)重载方法,此方法内部执行如下过程:

  • 解析输入流,将结果保存到_properties属性中
  • 利用_properties中的信息来实例化所有的Appender对象
  • 实例化所有的Category对象

instantiateAllAppenders

此方法内部实例化所有的Appender对象,并且从这个方法的内部实现中,我们可以知道配置文件到底可以怎么写。

void PropertyConfiguratorImpl::instantiateAllAppenders() {
        std::string currentAppender;
		
		// 配置文件中定义一个appender,都要如下格式
		// appender.appenderName = AppenderType
		// appender.appenderName.attribute = attributeValue
        std::string prefix("appender");
         // 因为_properties是一个map子类型的对象,所以他有lower/upper_bound方法,利用这个方法 非常高效地查找到指定的范围
   		 // 小提示:map容器虽然是无序容器,但是他的内部的key在容器中存放都是有序的:按照顺序从小到大
        Properties::const_iterator from = _properties.lower_bound(prefix + '.');//定位到的元素位置key >= prefix + '.'
        Properties::const_iterator to = _properties.lower_bound(prefix + '/'); //定位到的元素位置key >= prefix + '/'

        for(Properties::const_iterator i = from; i != to; ++i) {
            const std::string& key = (*i).first;
            const std::string& value = (*i).second;
            std::list<std::string> propNameParts;
            std::back_insert_iterator<std::list<std::string> > pnpIt(propNameParts);
            StringUtil::split(pnpIt, key, '.');     //这儿是为了分离出key的所有的以'.'分割的字符
            std::list<std::string>::const_iterator i2 = propNameParts.begin();
            std::list<std::string>::const_iterator iEnd = propNameParts.end();
            // 下面这儿进行这样判断原因是appender后面至少有一个.,然后后面接他的名称
            if (++i2 == iEnd) {
                throw ConfigureFailure(std::string("missing appender name"));
            }
			 // 注意appender配置的格式:
	        // appender.appenderName = AppenderType
	        // appender.appenderName.attribute = attributeValue
	        // 所以appender点号后面的字符肯定是appenderName
            const std::string appenderName = *i2++;

            // 其实第一次解析到的包含有appenerName的配置肯定是不带属性的,因为遍历map容器的元素key是从小到大的
            if (appenderName == currentAppender) {
  
            } else {
                if (i2 == iEnd) {  // 说明符合appender.appenderName = AppenderType格式
                    currentAppender = appenderName;
                    _allAppenders[currentAppender] = 
                        instantiateAppender(currentAppender);// 实例化当前appener,只是用上面提取的名称来调用instantiateAppender实例化Apepnder对象
                                                          // 在说明一下是使用instantiateAppender来实例化appender对象的

                } else {
	                // 执行到这种情况是只有appender的属性定位,例如:
	                // 只有:appender.appenderName.attribute = attributeValue
	                // 没有:appender.appenderName = appenderType
                    throw ConfigureFailure(std::string("partial appender definition : ") + key);
                }
            }                            
        }
    }

我们可以发现,上面内部使用appender+’.’ 和appender + ‘/’ + 1作为map的lower_bound和upper_bound为参数来确定 所有关于appender配置的区间段,然后提取出区间段内的所有appender名称,并且调用instantiateAppender来实例化单个的appender对象。

instantiateAppender

// 实例化单个指定名称的Appender对象
// 我们还要记住Appender属性的设置格式:
// appender.appenderName = AppenderType
// appender.appenerName.attribute = attributeValue

Appender* PropertyConfiguratorImpl::instantiateAppender(const std::string& appenderName) {
        Appender* appender = NULL;
        std::string appenderPrefix = std::string("appender.") + appenderName;//这儿就是设置appender属性用统一的格式
                                                                             //对于同一个appenderName的相关属性设置都有统一的格式


       // 下面这儿判断主要为了考虑到一种情况,这个方法被单独调用,而不是instantiateAllAppenders内部(怕被单独调用为什么不设置成private呢????)
        Properties::iterator key = _properties.find(appenderPrefix);  
        if (key == _properties.end())
            throw ConfigureFailure(std::string("Appender '") + appenderName + "' not defined");
		
		// 对应情况看的话,(*key).second就是AppenderType
	    // 那么要解释一下为什么要向下边这儿来解析出appenerType的类型
	    // log4cpp他是兼容log4j配置文件的内容的,
	    // 所以对于某个appender的具体Appender对象类型可能有如下写法:
	    // appender.appenderName = org.appache.ConsoleAppender
	    // 那么对于这种情况,提取最后一个位置就可以将ConsoleAppender给截取出来
        std::string::size_type length = (*key).second.find_last_of(".");
        std::string appenderType = (length == std::string::npos) ?
            (*key).second : (*key).second.substr(length+1);

		// 下面执行所有的实例Appender操作,要注意他解析了哪些属性,那么我们在使用配置文件进行配置的时候
    	// 就可以来设置这些属性值,来控制创建的Appender对象的属性
  
        if (appenderType == "ConsoleAppender") {
            std::string target = _properties.getString(appenderPrefix + ".target", "stdout");
            std::transform(target.begin(), target.end(), target.begin(), ::tolower);
            if(target.compare("stdout") == 0) {
                appender = new OstreamAppender(appenderName, &std::cout);
            }
            else if(target.compare("stderr") == 0) {
                appender = new OstreamAppender(appenderName, &std::cerr);
            }
            else{
                throw ConfigureFailure(appenderName + "' has invalid target '" + target + "'");
            }
        }
        else if (appenderType == "FileAppender") {
	        // 对于FileAppender,内部解析的是fileName属性,和appender属性,那么我们可以向如下配置
		    // appender.appenderName.fileName = xxx.log
		    // appender.appenderName.append = true/false
		    // 其实这两个属性和FileAppender的构造器参数是吻合的,所以不看这个实现,看对于Appender类型的构造器,也能够知道点,
		    // 但是有例外,比如FileAppender中还有个mode参数,这儿内部就没有解析
            std::string fileName = _properties.getString(appenderPrefix + ".fileName", "foobar");
            bool append = _properties.getBool(appenderPrefix + ".append", true);
            appender = new FileAppender(appenderName, fileName, append);
        }
        else if (appenderType == "RollingFileAppender") {
	        // 对于RollingFileAppender,因为他是FileAppender的基类,所以也要解析fileName和append,除此之外还要
		    // 解析maxFileSize, maxBackupIndex
		    // 配置文件可以像这样写:
		    // appender.appenderName.fileName = xxx.log
		    // appender.appenderName.append = true/false
		    // appender.appenderName.maxBackupSize = 4028
		    // appender.appenderName.maxBackupIndex = 10
            std::string fileName = _properties.getString(appenderPrefix + ".fileName", "foobar");
            size_t maxFileSize = _properties.getInt(appenderPrefix + ".maxFileSize", 10*1024*1024);
            int maxBackupIndex = _properties.getInt(appenderPrefix + ".maxBackupIndex", 1);
            bool append = _properties.getBool(appenderPrefix + ".append", true);
            appender = new RollingFileAppender(appenderName, fileName, maxFileSize, maxBackupIndex,
                append);
        }
        else if (appenderType == "DailyRollingFileAppender") {
          // 对于DailyRollingFileAppender,因为他是FileAppender的基类,所以也要解析fileName和append,除此之外还要
		    // 解析maxDaysKeep(注意了不是maxDaysToKeep)
		    // 那么可以向如下配置:
		    // appender.appenderName.fileName = xxx.log
		    // appender.appenderName.append = true/false
		    // appender.appenderName.maxDaysKeep = 20
            std::string fileName = _properties.getString(appenderPrefix + ".fileName", "foobar");
            unsigned int maxDaysKeep = _properties.getInt(appenderPrefix + ".maxDaysKeep", 0);
            bool append = _properties.getBool(appenderPrefix + ".append", true);
            appender = new DailyRollingFileAppender(appenderName, fileName, maxDaysKeep, append);
        }
#ifndef LOG4CPP_DISABLE_REMOTE_SYSLOG
        else if (appenderType == "SyslogAppender") {
      
            std::string syslogName = _properties.getString(appenderPrefix + ".syslogName", "syslog");
            std::string syslogHost = _properties.getString(appenderPrefix + ".syslogHost", "localhost");
            int facility = _properties.getInt(appenderPrefix + ".facility", -1) * 8; // * 8 to get LOG_KERN, etc. compatible values. 
            int portNumber = _properties.getInt(appenderPrefix + ".portNumber", -1);
            appender = new RemoteSyslogAppender(appenderName, syslogName, 
                                                syslogHost, facility, portNumber);
        }
#endif // LOG4CPP_DISABLE_REMOTE_SYSLOG
#ifdef LOG4CPP_HAVE_SYSLOG
        else if (appenderType == "LocalSyslogAppender") {
            std::string syslogName = _properties.getString(appenderPrefix + ".syslogName", "syslog");
            int facility = _properties.getInt(appenderPrefix + ".facility", -1) * 8; // * 8 to get LOG_KERN, etc. compatible values. 
            appender = new SyslogAppender(appenderName, syslogName, facility);
        }
#endif // LOG4CPP_HAVE_SYSLOG
        else if (appenderType == "AbortAppender") {
            appender = new AbortAppender(appenderName);
        }
#ifdef LOG4CPP_HAVE_LIBIDSA
        else if (appenderType == "IdsaAppender") {
            // default idsa name ???
            std::string idsaName = _properties.getString(appenderPrefix + ".idsaName", "foobar");

            appender = new IdsaAppender(appenderName, idsaname);
        }
#endif	// LOG4CPP_HAVE_LIBIDSA
        else {
            throw ConfigureFailure(std::string("Appender '") + appenderName + 
                                   "' has unknown type '" + appenderType + "'");
        }


		// 当时介绍Appender类时候,说这个方法是为了辅助配置实现的,现在应该知道了吧,如果没有这个方法
   		 // 我们无法区分是否要实例化Layout对象
        if (appender->requiresLayout()) {
            setLayout(appender, appenderName);// 内部实例化Layout对象,并且把这个Layout对象设置给Appender对象
        }

        // 设置阀值
   	 // 可以向这样配置:appender.appenderName.threshold = DEBUG
        std::string thresholdName = _properties.getString(appenderPrefix + ".threshold", "");
        try {
            if (thresholdName != "") {
                appender->setThreshold(Priority::getPriorityValue(thresholdName));    //内部会转换为Priority::Value类型
            }
        } catch(std::invalid_argument& e) {
	    delete appender;	// fix for #3109495
            throw ConfigureFailure(std::string(e.what()) + 
                " for threshold of appender '" + appenderName + "'");
        }

        return appender;
    }

总结:

  • 首先是提取取具体的要创建的Appender的类型, 然后根据创建的具体Appender类型,来获取响应的属性,然后调用对应Appender类型的构造方法来构造一个Appender对象。
  • 然后根据Appender对象的requiresLayout方法来决定是否实例化对象,如果实例化则调用setLayout(appender, appenderName)方法 来实例化具体的Layout对象,并且将之设置给appender对象。
  • 最后解析appenderName的threshold设置,如果设置的话,则先利用Priority::getPriorityValue方法来讲字符串转换为Priority::Value, 最后讲个值设置给上面构造的Appender对象。
  • 最后将这个值返回回去。

上面我们提到利用setLayout(appender, appenderName)方法来实例化Layout对象,下面来看一下这个源码的实现。

setLayout

// 实例化指定appenderName的Layout对象,并且将这个对象设置给appender
// appender的配置格式:
// appender.appenderName = AppenderType
// appender.appenderName.attribute = attributeValue
// appender.appenderName.layout = LayoutType
// appender.appenderName.coversionPattern = conversionPattern
void PropertyConfiguratorImpl::setLayout(Appender* appender, const std::string& appenderName) {
        std::string tempString;
        Properties::iterator key = 
            _properties.find(std::string("appender.") + appenderName + ".layout");//查找的是appender.appenderName.layout为key


		 // 当存在说明配置了LayoutType类型,否则则是用户遗漏了这个配置,下面则会抛出一个异常
        if (key == _properties.end())
            throw ConfigureFailure(std::string("Missing layout property for appender '") + 
                                   appenderName + "'");
		
		// 下面的这样实现,前面一个函数已经介绍过了
	    // 还是为了log4j和log4cpp配置文件的兼容性
	    // 最后执行完毕,layoutType就是具体的Layout的类型
        std::string::size_type length = (*key).second.find_last_of(".");
        std::string layoutType = (length == std::string::npos) ? 
            (*key).second : (*key).second.substr(length+1);
 
        Layout* layout;
        // 实例化对应的Layout对象
        if (layoutType == "BasicLayout") {
            layout = new BasicLayout();
        }
        else if (layoutType == "SimpleLayout") {
            layout = new SimpleLayout();
        }
        else if (layoutType == "PatternLayout") {
            PatternLayout* patternLayout = new PatternLayout();

			 // 解析是appender.appenderName.conversionPattern属性
            key = _properties.find(std::string("appender.") + appenderName + ".layout.ConversionPattern");
            if (key == _properties.end()) {
                //  即使没有设置PatternLayout内部也有一个默认的conversionpattern属性:"%m%n"
            } else {
                patternLayout->setConversionPattern((*key).second);  //只有有此属性的时候才会设置对应的属性
            }

            layout = patternLayout;
        }
        else {
            throw ConfigureFailure(std::string("Unknown layout type '" + layoutType +
                                               "' for appender '") + appenderName + "'");
        }

        appender->setLayout(layout);  // 将实例化好的layout设置给appender对象

    }

小结: 首先解析出具体的LayoutType,然后根据对应的具体类型 来解析对应的参数然后实例化Layout对象,最后将实例化好的对象设置给Appender对象。

到目前位置实例化Appender的部分已经讲解完毕了,实例化结果是保存到_allAppenders。其实我们是没有必要构建这个map的, 所有Appender类内部已经有一个类似的map了,我们当要查询某个Appender的时候,完全可以直接调用Appender::getAppender方法来进行查询。

我们回想一下,doConfigure(std::istream&)内部,首先实例化所有的Appender,然后,获取所有category的名称,然后遍历每个category的名称来构建具体的category对象。下面我们来看一下获取所有对象的名称getCategories方法。

getCategories

void PropertyConfiguratorImpl::getCategories(std::vector<std::string>& categories) const {
        categories.clear();

      
        categories.push_back(std::string("rootCategory"));   //第一个元素肯定是rootCategory

		// Category的配置格式是:
    	//rootCategory = PriorityLevel, appenderName1, appenderName2, ... #配置根
   		 //category.son = PriorityLevel, appenderName1, appenderName2, ... #配置孩子

  
        // 利用map key的有序性(指的是值是从小到大排序的),
	    // 执行过下面的两次lower_bound
	    // [from,to) 就是[第一个子category定义, 第一个不是category定义的地方),因为只有子category后面有点
        std::string prefix("category");
        Properties::const_iterator from = _properties.lower_bound(prefix + '.');
        Properties::const_iterator to = _properties.lower_bound(prefix + (char)('.' + 1)); 
        for (Properties::const_iterator iter = from; iter != to; iter++) {
            categories.push_back((*iter).first.substr(prefix.size() + 1));  //这儿提取是子category的名称
        }
    }

首先清空原来categories中的内容,然后因为rootCategory是首先存在的,所以他先将rootCategory添加到categories中, 再次利用map的lower_bound方法来定位到所有category.rootCategory.xxx的区间块中,将这个区间块中的所有xxx的内容 添加到categories中。

configureCategory

// 实例化指定categoryName名称的Category对象
// 切记Category配置格式:
//rootCategory = PriorityLevel, appenderName1, appenderName2, ... #配置根
//category.son = PriorityLevel, appenderName1, appenderName2, ... #配置孩子
void PropertyConfiguratorImpl::configureCategory(const std::string& categoryName) {
        // // tempCatName相当于是一个具体的category前缀或者说是当前categoryName对应的map中的key
        std::string tempCatName = 
            (categoryName == "rootCategory") ? categoryName : "category." + categoryName;

		// 查找对应_properties中categoryName的位置
        Properties::iterator iter = _properties.find(tempCatName);

        if (iter == _properties.end())
            throw ConfigureFailure(std::string("Unable to find category: ") + tempCatName);

        // 实例化category对象
   		// 这儿特别除了了当categoryName=="rootCategory"的时候,返回的是根Category对象
    	// 否则对应categoryName是sub, 或者sub1.sub2的情况调用的是getInstance,他会自动构建需要的Category对象
    	// 来形成对应的Category对象链,可以参照HierachyMaintainer::getInstance方法的实现
        Category& category = (categoryName == "rootCategory") ?
            Category::getRoot() : Category::getInstance(categoryName);

        // 就是将右值就是以','来进行划分,结果保存在tokens中
	    // 假如说,这儿右值为:PriorityLevel, appenderName1, appenderName2
	    // 那么tokens就是PriorityLevel, appenderName1, appenderName2
        std::list<std::string> tokens;
        std::back_insert_iterator<std::list<std::string> > tokIt(tokens);
        StringUtil::split(tokIt, (*iter).second, ',');
        std::list<std::string>::const_iterator i = tokens.begin();
        std::list<std::string>::const_iterator iEnd = tokens.end();

		// i肯定是指向权限那个位置(这儿规定了,如果不设置权限,那么一定要写一个空',')
        Priority::Value priority = Priority::NOTSET;
        if (i != iEnd) {   
        	// 执行过下面代码后,i指向第一个Appender名称的位置
            std::string priorityName = StringUtil::trim(*i++);   //这儿是获取设置的权限值
            // 从下面的实现我们可以看到,权限可以为空,但是不能够不能没有空',',因为如果不写的话,他会将appenderName解析为权限
      		//那么Priority::getPriorityValue这个方法内部肯定会抛出一行的。
            try {
                if (priorityName != "") {
                    priority = Priority::getPriorityValue(priorityName);
                }
            } catch(std::invalid_argument& e) {
                throw ConfigureFailure(std::string(e.what()) + 
                    " for category '" + categoryName + "'");
            }
        }
	
		//设置权限等级,如果没有设置的话,那么权限等级就是Priority::NOTSET
	    //这儿需要注意的是,因为root Category的默认权限是Priority::INFO
	  	//如果使用配置文件来创建root Category的话,并且没有设置rootCategory的权限,这儿也会导致根Category的权限为Priority::NOTSET
		//但是Category::getChainedPriorit内部有个循环假设条件就是根节点应该是非Pirority::NOTSET的值
	   //这儿配置加载时候,可能会将根Category设置为Priority::NOTSET,那么在执行getChainedPriority这个方法就会有报错的风险。
	    //所以处理这个问题请一定要设置rootCategory的权限属性,不能留空
        try {
        	category.setPriority(priority);
        } catch (std::invalid_argument& e) {
        	throw ConfigureFailure(std::string(e.what()) +
                    " for category '" + categoryName + "'");
        }

		//允许配置
    	//additivity.categoryName = true/false #categoryName可以是rootCategory
        bool additive = _properties.getBool("additivity." + categoryName, true);
        category.setAdditivity(additive);

        category.removeAllAppenders(); //移除原来的内部appender
        // 挨个遍历每一个appender名称,然后从_allAppenders获取对应的值,然后将他们添加到
   	 // 如果指定的appenderName不存在,则会抛异常的
       for(/**/; i != iEnd; ++i) {           
            std::string appenderName = StringUtil::trim(*i);
            AppenderMap::const_iterator appIt = 
                _allAppenders.find(appenderName);
            if (appIt == _allAppenders.end()) {
                // appender not found;
                throw ConfigureFailure(std::string("Appender '") +
                    appenderName + "' not found for category '" + categoryName + "'");
            } else {
                category.addAppender(*((*appIt).second));
            }
        }
    }

看过这儿的代码,我们知道category的设置格式是: rootCategory = 权限, appenderName1, appendName2… 或者 category.subCategoryName = 权限, appenderName1, appenderName2…

上面代码内部首先根据是上面哪一种形式来创建合适的Category对象

  • 对于第一种清创使用的是Category::getRoot方法
  • 对于第二种 情况使用的是Category::getInstance方法。

然后设置上面创建好的category对象的权限,注意右值后面权限后面的那个’,'一定要有,前面权限的具体内容可以是可选的,对于rootCategory 建议你设置一个具体的权限字符串,否则假如不设置的话,那么rootCategory的权限会被设置Priority::NOTSET,这肯定是违反的Category内部 根Category的默认设置

然后根据右侧权限后面的appenderName来从_allAppenders中进行查找(已经在doConfigure方法执行过程的第一步instantiateAllAppenders), 然后将找到的值添加到上面新建的Category对象中。

小结

  • 实现实例化号所有配置文件中的Appender对象,内部也会实例化号Appender对象内部的Layout对象
  • 从配置文件中获取所有的Category名称
  • 遍历上面获取的category名称,3.遍历上面获取的Category名称,根据名称来实例化好对应的Category对象

总结

  • Properties就是一个map<string,string>的子类,它提供了load/save来从配置文件中加载信息,或者从配置文件中 保存信息,并且在加载过程中会忽略掉注释,替换掉右值中的形如${xx}的环境变量部分。
  • PropertyConfigurator内部真正调用的是PropertyConfigurator的doConfigure方法。
  • PropertyConfigurator::doConfigure方法的真正解析流程是在doConfigure(std::istream&)那个重载方法中, 内部首先检测所有Appender和与之对应的Layout,然后才会解析Category,其实因为Category的右值是参考已经创建好的Appender的名称,所以 本应该按照这样的顺序进行解析。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值