log4cpp源码阅读:PropertyConfigurator解析
发布日期:2022-03-16 03:25:44 浏览次数:25 分类:技术文章

本文共 27872 字,大约阅读时间需要 92 分钟。

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

Properties

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

// 需要特别注意,他是map
的子类class Properties : public std::map
{
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不存在,则返回defaultValueint 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
{
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, A2log4j.appender.rootAppender=org.apache.log4j.ConsoleAppender log4j.appender.rootAppender.layout=org.apache.log4j.BasicLayoutlog4j.appender.A1=org.apache.log4j.FileAppender log4j.appender.A1.fileName=A1.log log4j.appender.A1.layout=org.apache.log4j.BasicLayoutlog4j.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
AppenderMap; PropertyConfiguratorImpl(); virtual ~PropertyConfiguratorImpl(); ...protected: Properties _properties; //可以从配置文件中加载信息的一个map
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
catList; getCategories(catList); //内部实现是获取所有category的名称到catList中,我们暂且也不看 // 配置每一个category对象 for(std::vector
::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
propNameParts; std::back_insert_iterator
> pnpIt(propNameParts); StringUtil::split(pnpIt, key, '.'); //这儿是为了分离出key的所有的以'.'分割的字符 std::list
::const_iterator i2 = propNameParts.begin(); std::list
::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 = attributeValueAppender* 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 = conversionPatternvoid 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
& 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
tokens; std::back_insert_iterator
> tokIt(tokens); StringUtil::split(tokIt, (*iter).second, ','); std::list
::const_iterator i = tokens.begin(); std::list
::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的名称,所以 本应该按照这样的顺序进行解析。

转载地址:https://blog.csdn.net/zhizhengguan/article/details/123231777 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:C/C++编程:图床项目部署
下一篇:网络:数据包、报文、帧、分组、段

发表评论

最新留言

不错!
[***.144.177.141]2024年04月20日 06时12分01秒