PropertyConfigurator可以帮助我们从一个配置文件中来自动创建好Category对象、Appender对象、Layout对象,并且自动设置好它们的对应关系。然后我们通过Category::getInstance方法就获取配置文件所设置的category名称,直接进行日志操作。省去了我们自己构造Appender对象,自己构造Layout。非常方便。
Properties
这个类似就是把形如:name = ffx
、age = 24
、student_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的名称,所以 本应该按照这样的顺序进行解析。