log4cpp源码阅读:Category组件学习
2022/3/2 12:14:59
本文主要是介绍log4cpp源码阅读:Category组件学习,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Category组件在Log4Cpp库中,是真正的Logging对象。我们可以使用category对象来进行各种级别的日志操作,比如debug、warn、info…并且这个对象还可以添加各种appender,用来将日志指令分发给各个appender对象来输出到最终目的地上
Category与Appender对象之间的关系(观察者模式)
category与appender之间是一种目标和观察者关系。也就是说它们之间使用的设计模式就是观察者设计模式:category对象会将具体的debug、warn、info…指令分发给内部添加的append对象。
添加/移除Appender对象
class Category { private: typedef std::map<Appender *, bool> OwnsAppenderMap; AppenderSet _appender; //AppenderSet的具体类型是:typedef std::set<Appender *> AppenderSet;在Appender.hh文件中被定义 OwnsAppenderMap _ownsAppender; // 用来复制实现内部添加的Appender对象能够被安全delet的一个辅助信息 mutable threading::Mutex _appenderSetMutex; //锁:一个辅助数据能够安全访问的手段 public: virtual void addAppender(Appender* appender); virtual void addAppender(Appender& appender); virtual void removeAllAppenders(); virtual void removeAppender(Appender* appender); virtual bool ownsAppender(Appender* appender) const throw(); } // 将指定的appender对象,添加到内部的管理容器中,参数无效会抛出std::invalid_argument异常 void Category::addAppender(Appender* appender) { if (appender) { threading::ScopedLock lock(_appenderSetMutex); { // 首先检测指定的appender是否已经存在了,只有不存在的时候,才会添加,否则肯定什么也不用做 // 因为传过来的是指针,如果相同的话,则说明根本就是同一个东西 AppenderSet::iterator i = _appender.find(appender); if (_appender.end() == i) { // not found _appender.insert(appender); // 执行真正的添加操作 _ownsAppender[appender] = true; } } } else { throw std::invalid_argument("NULL appender"); // 注意第二个参数设置为true(当category类执行移除操作的时候,你会知道执行他的用意) } } // 另一重载的appender方法 // 执行的逻辑和addAppender(Appender*)版本基本类似,因为appender是引用,那么他肯定是已经存在的对象,所以不用进行空值检测 // 并且也是在已经存在列表中进行查找,如果不存在的话,他才会添加新的内容 // 唯一不一样的是(这儿需要注意):当发现appender是还没有添加的appender的时候,除了将appender插入到_appender容易中,还 // _ownsAppender[&appender] = false; 注意这儿设置为false, 而上面那个版本设置为true void Category::addAppender(Appender& appender) { threading::ScopedLock lock(_appenderSetMutex); { AppenderSet::iterator i = _appender.find(&appender); if (_appender.end() == i) { _appender.insert(&appender); _ownsAppender[&appender] = false; } } } void Category::removeAllAppenders() { threading::ScopedLock lock(_appenderSetMutex); { for (AppenderSet::iterator i = _appender.begin(); i != _appender.end(); i++) { // found OwnsAppenderMap::iterator i2; if (ownsAppender(*i, i2)) { delete (*i); } } _ownsAppender.clear(); //将两个用来记录appender的信息的容器中的信息完全删除 _appender.clear(); } } // 将当前appender从Category移除 void Category::removeAppender(Appender* appender) { threading::ScopedLock lock(_appenderSetMutex); { AppenderSet::iterator i = _appender.find(appender); //首先进行查找 if (_appender.end() != i) { OwnsAppenderMap::iterator i2; // 用来检测当前category对象,是否完全属于appender // 如果是的话,会将这个appender完全delete掉,然后也会将_ownsAppender中这个map对象的存在也移除掉 if (ownsAppender(*i, i2)) { _ownsAppender.erase(i2); delete (*i); } _appender.erase(i); // 不管是否属于肯定会将这个对象从_appender中移除掉 } else { // appender not found } } } // 检测当前category对象是否完全拥有appender对象(只要完全拥有它才有权使用delete来删除) bool Category::ownsAppender(Appender* appender) const throw() { bool owned = false; threading::ScopedLock lock(_appenderSetMutex); { if (NULL != appender) { OwnsAppenderMap::const_iterator i = _ownsAppender.find(appender); if (_ownsAppender.end() != i) { owned = (*i).second; } } } return owned; }
对于目标注册/反注册观察者的方法,总结如下:
- Category实际上使用AppenderSet _appender,来维护添加进来的Appender对象
- Category还使用OwnsAppenderMap _ownsAppender, 来辅助remove方法能够完全释放掉所有已经添加进来的Appender
- Category中的Appender方法,就是类似于观察者模式中的Subject的register方法,而remove方法就类似于Subject中的Unregister方法,可以看到Category完全就是充当一个subject的角色
callAppenders方法(观察者模式中的通知方法)
void Category::callAppenders(const LoggingEvent& event) throw() { threading::ScopedLock lock(_appenderSetMutex); { // 挨个遍历每一个Appender对象,调用appender对象的doAppend方法 if (!_appender.empty()) { for(AppenderSet::const_iterator i = _appender.begin(); i != _appender.end(); i++) { (*i)->doAppend(event); } } } // 下面的代码,你可以暂时不用看,等学完Category的自身结构的时候就明白了 if (getAdditivity() && (getParent() != NULL)) { getParent()->callAppenders(event); } }
我们可以看到,Category::callAppender方法就类似于观察者模式中的Subject的notify方法,而Appender::doAppend方法就类似于观察者模式中的Observer的receive方法。
Category自身的结构(责任链模式)
源码
class Category { protected: // 唯一一个可以被子类或者友元调用的构造器 // name是当前category对象的名称 // parent 最后会被设置到_parent属性中(他是我们目前为止唯一需要关心) // priority 表示这个日志能够处理的最低优先级,如果低于这个优先级的日志操作是不会真正进行的 Category(const std::string& name, Category* parent, Priority::Value priority = Priority::NOTSET); private: Category(const Category& other); //禁止拷贝和赋值 Category& operator=(const Category& other); Category* _parent; // 执行父category const std::string _name; //代表category对象的名称 volatile Priority::Value _priority; //代码这个对象所能处理日志的操作的最低优先级 volatile bool _isAdditive; //这个属性,我们暂时不用考虑 } Category::Category(const std::string& name, Category* parent, Priority::Value priority) : _name(name), _parent(parent), _priority(priority), _isAdditive(true) { }
从上面可以看出,Category是一种链式的结果,一个Category对象,还可以指向一个父Category对象,然后父Category对象还可以拥有它自己的父Category,以此类推,可以形成一条很长的Category链。
还可以看到,Category拥有一个唯一的构造器,而且这个构造器是protected的。那么我们怎么实例化它呢?难道我们要继承这个Category类吗?其实Category对象还提供了几个工厂方法。
我们先不要想这些工厂方法,我们先来看一看Category的属性:
- name就是Category类型的名称
- _priority,本质上是一个int类型,Category类的具体日志操作时使用
- _isAdditive,我们来具体看一看。因为我们讲到Category::callAppends方法的时候,里面有一段 代码没有讲,现在我们已经完成了所有必要的基础了,我们回过头来看一下:
void Category::callAppenders(const LoggingEvent& event) throw() { threading::ScopedLock lock(_appenderSetMutex); { // 挨个遍历每一个Appender对象,调用appender对象的doAppend方法 。。。。。。 } // getAdditivity()就是_isAdditivity属性的访问方法, // getParent()获取的是_parent属性 // 我们可以看到下面的逻辑是,如果_isAdditivity==true,则会调用 // 父亲的callAppenders方法,但是父亲也是一个Category对象啊,他仍然会 // 调用自己已经添加Appender对象的doAppend方法,然后检测自己的_isAdditivity,来决定是否将这个LoggingEvent // 继续向上传送处理 if (getAdditivity() && (getParent() != NULL)) { getParent()->callAppenders(event); } }
从这里我们可以看到,其实Category类,还用到了责任链模式:在构造的时候,它会将父Category对象传进来,当执行callAppenders方法的时候,它可以根据_isAdditivity属性的设置情况将event传递给它的父category对象。
到此整个Category的观察者模式部分已经彻底讲解完毕。
Category的工厂方法(工厂方法)
我们先来看下Category::getRoot方法,和Category::getInstance方法的源码
Category& Category::getRoot() { return getInstance(""); } Category& Category::getInstance(const std::string& name) { return HierarchyMaintainer::getDefaultMaintainer().getInstance(name); }
我们可以看到getRoot静态方法内部调用的是getInstance静态方法,而getInstance静态方法内部调用的是 HierarchyMaintainer::getDefaultMaintainer().getInstance(name)
又:
class Category { friend class HierarchyMaintainer; 。。。 };
即HierarchyMaintainer是Category的友元类,我们再想到,Category提供了一个唯一实现的构造方法,这个构造方法是protected的。可以推测出,HierarchyMaintainer是Category的工厂类。
HierarchyMaintainer
Category的工厂类,内部负责所有Category对象的构造操作。
我们先来看一下它的数据成员
class HierarchyMaintainer { public: typedef std::map<std::string, Category*> CategoryMap; protected: CategoryMap _categoryMap; }
内部包含了一个成员_categoryMap。HierarchyMaintainer 内部创建的Category对象都会保存在_categoryMap容器中,都是通过Category::getInstance方法内部调用的。
下面以HierarchyMaintainer::getDefaultMaintainer().getInstance(name);为引,来看一下整个HierarchyMaintainer类的功能
HierarchyMaintainer::getDefaultMaintainer()方法(一种单例工厂方法的一种实现方式)
class HierarchyMaintainer { public: static HierarchyMaintainer& getDefaultMaintainer(); } // 这个方法是静态的 // 当这个方法第一次被调用的时候,defaultMaintainer会被构造,然后将之返回 // 当下次这个方法被调用时,会直接将此静态对象返回 // 这是单例模式的一种典型实现方式 HierarchyMaintainer& HierarchyMaintainer::getDefaultMaintainer() { static HierarchyMaintainer defaultMaintainer; return defaultMaintainer; }
可以看到,getDefaultMaintainer()其实是HierarchyMaintainer类的一个单例工厂方法。所以HierarchyMaintainer::getDefaultMaintainer().getInstance(name); 内部真正getInstance方法就是HierarchyMaintainer::getInstance方法
HierarchyMaintainer::getInstance方法
class HierarchyMaintainer { public: virtual Category& getInstance(const std::string& name); } Category& HierarchyMaintainer::getInstance(const std::string& name) { threading::ScopedLock lock(_categoryMutex); // 确保getInstance()能够多线程安全的访问 return _getInstance(name); //真正的调用的是_getInstance方法 }
这个方法其实是一个包装方法,它是线程安全的,内部调用的是_getInstance方法。
class HierarchyMaintainer { protected: virtual Category* _getExistingInstance(const std::string& name); virtual Category& _getInstance(const std::string& name); } Category* HierarchyMaintainer::_getExistingInstance(const std::string& name) { Category* result = NULL; CategoryMap::iterator i = _categoryMap.find(name); if (_categoryMap.end() != i) { result = (*i).second; } return result; } Category& HierarchyMaintainer::_getInstance(const std::string& name) { Category* result; // 就是就是检测指定name名称的Cateogory对象是否已经被创建,如果创建好的则将之返回(就是利用利用_categoryMap的find方法) result = _getExistingInstance(name); if (NULL == result) { // 指定名称的category对象还没有被创建,需要创建一个 // name为"",说明创建的是root if (name == "") { // 第二个参数为NULL,表示没有父category指针(根没有父category) // 即Category::getRoot方法实现是:HierarchyMaintainer::getDefaultMainter().getInstance(""); result = new Category(name, NULL, Priority::INFO); } else { // name的格式可以包含多个点: // 比如 parentparent.parent.son.sonson std::string parentName; size_t dotIndex = name.find_last_of('.'); //首先查找的最后一个.的位置 if (name.length() <= dotIndex) { // 这儿为什么是小于等于呢?是因为当name没有找到'.'的时候返回的是std::size_t(-1) parentName = ""; // 这个值是size_t即unsigned int(是无符号)中最大的值,没有比他再大了 } else { parentName = name.substr(0, dotIndex); //不包最后点的名称。比方name是parentparent.parent.son.sonson,那么这儿parentName就是parentparent.parent.son } Category& parent = _getInstance(parentName); // 递归调用,相当于先创建父category result = new Category(name, &parent, Priority::NOTSET); // 然后使用创建号的父category来创建子category } _categoryMap[name] = result; //最终创建号的category对象,会保存到_categoryMap属性中,以category对象的name为key } return *result; }
这个方法是整个工厂方法的核心所在,主要执行如下流程:
- 检测name是否已经创建号了,如果创建号了,则将之返回
- 检测name是否为空,如果是,那么创建一个根节点(有一个细节,根节点的priority属性默认设置为Priority::INFO),并且将这个新建的节点保存到_categoryMap属性中
- 3.如果name不为空,那么他首先先递归调用_getInstance方法获取 name名称所指定的父节点(比如name是parent.son,那么他会使用 _getInstance(“parent”)来创建父节点),然后使用创建号的父category对象来新建一个子category,也就是指定name的category对象。(这儿 也有一个细节,非根节点的priority属性设置为Priority::NOTSET),并且将之添加到_categoryMap中(以name为key,以新建的category指针为值)
- 上面递归调用中,比如name=parent.son,那么它会调用_getInstance(“parent”),然后这个方法内部 又会调用_getInstance(""),此方法返回根节点记为root,然后继续回到_getInstance(“parent”)中,新建一个Category(“parent”, root, Priority::NOTSET),假设为p,然后将这个对象返回,继续回到_getInstane(“parent.son”)来执行,则会返回Category(“parent.son”, p, Priority:NOTSET)
根据这个_getInstance方法的实现我们可以知道:
- 首先指定的name可以包含几个’.’
- 根Category对象的默认_priority属性是Priority::INFO,其他的非根Category对象的_priority属性Priority::NOTSET属性。
- 所有创建好的Category对象,都被备份到HieratchyMaintainer的_categoryMap属性中
HierarchyMaintainer::~HierarchyMaintainer的析构方法
class HierarchyMaintainer { public: virtual ~HierarchyMaintainer(); } HierarchyMaintainer::~HierarchyMaintainer() { shutdown(); deleteAllCategories(); }
我们可以看到执行了shutdown()方法和deleteAllCategories()方法。
HierarchyMaintainer::shutdown方法
下面我们看一下shutdown()方法,但是在看这个方法之前,我们要看一下准备:
class HierarchyMaintainer { public: typedef void (*shutdown_fun_ptr)(); //定义了一种不带参数没有返回值的函数指针类型,其实就是一种类型回调函数 void register_shutdown_handler(shutdown_fun_ptr handler); //注册指定回调函数到handlers中 private: typedef std::vector<shutdown_fun_ptr> handlers_t; //用来保存函数指针的容器类型 handlers_t handlers_; } //注册指定回调函数到handlers中 void HierarchyMaintainer::register_shutdown_handler(shutdown_fun_ptr handler) { handlers_.push_back(handler); }
即HierarchyMaintainer体供了可以用来扩展的回调函数,至于这些回调函数的具体功能,我们需要看一下这个类的shutdown方法。
class HierarchyMaintainer { public: virtual void shutdown(); } void HierarchyMaintainer::shutdown() { threading::ScopedLock lock(_categoryMutex); //保证此方法是线程安全的 { // 清除所有的已经实例化好的Category对象内部的Appender对象(通过调用Category的removeAllAppenders方法来实现,这个方法我们前面已经讨论过) for(CategoryMap::const_iterator i = _categoryMap.begin(); i != _categoryMap.end(); i++) { ((*i).second)->removeAllAppenders(); } } // 调用使用register_shutdown_handler添加过来的回调函数 // 其实你可以看到,他也是有限制的,因为到有一个回调函数发生以后的时候,那么其他的回调函数都没有机会被执行了 // 不知道,是设置者是有心的还是无心的过失,个人认为可以将这个try ... catch...块添加到for循环的内部 try { for(handlers_t::const_iterator i = handlers_.begin(), last = handlers_.end(); i != last; ++i) (**i)(); } catch(...) { } }
简单总结一下HierarchyMaintainer::shutdown方法功能:
- 清除所有已经创建号的category对象内部已经添加的appener
- 调用所有已经注册进来的回调函数。需要注意的是:这个回调函数不能发生异常,否则可能终止这个回调函数后面注册进来的回调参数
HierarchyMaintainer::deleteAllCategories方法
class HierarchyMaintainer { public: virtual void deleteAllCategories(); } void HierarchyMaintainer::deleteAllCategories() { threading::ScopedLock lock(_categoryMutex); { for(CategoryMap::const_iterator i = _categoryMap.begin(); i != _categoryMap.end(); i++) { delete ((*i).second); } _categoryMap.clear(); } }
这个方法非常简单,直接delete掉_categoryMap内部的所有的成员,这样就不会发生内存泄露了
上面介绍的getInstance()方法和析构方法是整个类的核心功能所在,即用来创建Category对象,和用来维护已经创建好的Category对象。
小结:
- HierarchyMaintainer方法的工厂方法getInstance()和对每个category对象进行清理的shutdown()方法,和释放内部所有已经创建好的category对象的deleteAllCategories()方法,它们都是线程安全的
- 当HierarchyMaintainer对象被销毁的时候,它会自动调用shutdown()方法和deleteAllCategories()方法。,所以说,对于 整个程序来即使程序最后没有调用Category::shutdown()方法,也不会发生任何资源资料的。这就是说,这个工厂类来负责维护所有已经创建号的category对象的生命周期。
- 3.对于使用getInstance方法创建的含有父亲的category对象,他们的名称是包含有点的名称,而不是最后点号后面的名称,我们可以从 getInstance()中来寻找到原因。
其他方法:
// 是否存在name对象的category Category* HierarchyMaintainer::getExistingInstance(const std::string& name) { threading::ScopedLock lock(_categoryMutex); return _getExistingInstance(name); } // 当前已经创建的所有category std::vector<Category*>* HierarchyMaintainer::getCurrentCategories() const { std::vector<Category*>* categories = new std::vector<Category*>; threading::ScopedLock lock(_categoryMutex); { for(CategoryMap::const_iterator i = _categoryMap.begin(); i != _categoryMap.end(); i++) { categories->push_back((*i).second); } } return categories; }
Category::shutdown()
Category的资源回收方法
class Category { public: static void shutdown(); } void Category::shutdown() { HierarchyMaintainer::getDefaultMaintainer().shutdown(); }
Category对象的shutdown方法,内部实际上调用的是上一节中介绍的HierarchyMaintainer.shutdown方法。内部执行步骤可以简单回忆一下:
- 调用所有category的removeAllAppenders方法,来delete掉所有拥有的Appender对象,并且清空category对象内部的_appender容器中的内容,和ownsAppender容器中的内容、
- 调用HierarchyMaintainer内部注册进的回调函数。(其实对于这一步,通过阅读Category的整个实现,他并没有注册任何回调函数,也就是 说,这个拓展清理机制,在实际使用中并没有被用到。)
- 补充:所有创建的的category对象,其实并没有被销毁。它的销毁时机是HierarchyMaintainer::getDefaultMaintainer()方法内部定义的static变量被销毁的时候。这里只是销毁了所有被category对象完全拥有的Appender。
看一下Category对象的析构方法:
class Category { public: ~Category(); ...... }; ~Category::Category() {removeAllAppenders();}
就是移除内部所有的Appender对象。存在这个方法,单独上来考虑是合理的,但是因为Category对象的生命周期是完全由HierachyMaintainer对象 来维护的,而在HierachyMaintainer的shutdown方法中,拥有此方法的功能,在HierachyMaintainer的析构方法中,也有此功能,所以我认为, 要么这个类的析构方法中不做任何处理,要么将就HierachyMaintainer::shutdown方法,不要提供调用每一个category对象的removeAllAppenders这个流程。 我认为前者更合理,就是不在Category的析构方法中进行任何处理。
Category的日志方法
Category可以用来输出各种不同的日志,并且也处理了一种需求,比如某些情况下,我想输出各种类型的信息,包括错误信息、普通信息、调试信息,但是某些情况下,我们只想看到错误信息。category使用了Prority::Value _priority
属性来实现。
Priority这个类,其内部有一个PriorityLevel枚举类型,内部定义了各种枚举常量,定义了一个typedef int Value; 还定义了两工具函数,一个是getPriorityName,可以将Value类型转换为字符串类型,比如getPriorityName(Priority::DEBUG)返回"DEBUG",一个是 getPriorityValue,将字符串转换为Value,比如getPriorityValue(“DEBUG”) 则输出为Priority::DEBUG,这个方法字符串还可以是数字,比如 getPriorityValue(“123”)则返回则为123,一般不这样使用。
用来确定某种日志级别的操作是否执行的辅助方法是isPriorityEnabled,源码如下:
bool Category::isPriorityEnabled(Priority::Value priority) const throw() { return(getChainedPriority() >= priority); // 只有当设置的_priority值大于指定priority值的时候才会返回true } // 上面getChainedPriority方法的实现 Priority::Value Category::getChainedPriority() const throw() { // REQUIRE(rootCategory->getPriority() != Priority::NOTSET) ,这儿的条件肯定是成立的, //因为root对象使用HierachyMaintainer::getInstance(""),内部当name为空,他创建的root的priority值为Priority::INFO //所以只要不人为将root Category的priority设置为Priority::NOTSET,下面的程序就不会crush // 子 -> 父 -> 父。。。查找到第一个_priority属性不是设置为Priority::NOTSET,因为root的默认_priority为Priority::INFO // 这个循环是肯定能够终止的。 const Category* c = this; while(c->getPriority() >= Priority::NOTSET) { c = c->getParent(); } return c->getPriority(); } void Category::setPriority(Priority::Value priority) { if ((priority < Priority::NOTSET) || (getParent() != NULL)) { _priority = priority; } else { /* caller tried to set NOTSET priority to root Category. Bad caller! */ throw std::invalid_argument("cannot set priority NOTSET on Root Category"); } }
正如我们可以看到,当内部设置的_priority的值大于指定的priority值的时候,那么表示进行的操作都是满足的。
下面我们来看一下一个具体的记录日志方法,我们选择的是debug方法,对于其他的info, alert, warn。。。他们的组成都是相同的。我们可以看一下他们的 类似结构:
class Category { public: void debug(const char* stringFormat, ...) throw(); void debug(const std::string& message) throw(); inline bool isDebugEnabled() const throw() { return isPriorityEnabled(Priority::DEBUG); }; inline CategoryStream debugStream() { return getStream(Priority::DEBUG); } } class Category { public: void info(const char* stringFormat, ...) throw(); void info(const std::string& message) throw(); inline bool isInfoEnabled() const throw() { return isPriorityEnabled(Priority::INFO); }; inline CategoryStream infoStream() { return getStream(Priority::INFO); } } class Category { public: void notice(const char* stringFormat, ...) throw(); void notice(const std::string& message) throw(); inline bool isNoticeEnabled() const throw() { return isPriorityEnabled(Priority::NOTICE); }; inline CategoryStream noticeStream() { return getStream(Priority::NOTICE); } } 。。。。
下面仅以debug方面的方法来进行系统介绍。
void debug(const char* stringFormat, …)
void Category::debug(const char* stringFormat, ...) throw() { // 首先检测权限 if (isPriorityEnabled(Priority::DEBUG)) { va_list va; va_start(va,stringFormat); _logUnconditionally(Priority::DEBUG, stringFormat, va); //进行真正的日志操作 va_end(va); } }
可以看到,这个类会首先检测当前权限是否支持Priority::DEBUG,只要_priorty > Priority::DEBUG就支持。然后初始化可变参数进行真正的日志操作,然后释放可变参数。所以关键就是_logUnconditionally实现。
_logUnconditionally
class Category { protected: virtual void _logUnconditionally(Priority::Value priority, const char* format, va_list arguments) throw(); } void Category::_logUnconditionally(Priority::Value priority, const char* format, va_list arguments) throw() { _logUnconditionally2(priority, StringUtil::vform(format, arguments)); }
_logUnconditionally2
class Category { protected: virtual void _logUnconditionally2(Priority::Value priority, const std::string& message) throw(); } void Category::_logUnconditionally2(Priority::Value priority, const std::string& message) throw() { //组装一个日志事件对象:当前category对象的名称;需要写入的消息;当前线程的NDC值,如果没有使用NDC::put放入的话,则返回为空字符串;这个日志操作的等级 LoggingEvent event(getName(), message, NDC::get(), priority); callAppenders(event);// 将这个日志事件对象调用我们上面介绍的通知接口,将这个对象分发到各个appener中 } //--------------------- const std::string& Category::getName() const throw() { return _name; } //--------------------- const std::string& NDC::get() { if (isUsedNDC) return getNDC()._get(); else return emptyString; } const std::string& NDC::_get() const { static std::string empty = ""; return (_stack.empty() ? empty : _stack.back().fullMessage); }
总结一下void debug(const char* stringFormat, …)的执行流程:首先组织可变参数,然后调用_logUnconditionally分发,而_logUnconditionally内部利用StringUtil::vform方法将可变参数转换为字符串,然后调用 _logUnconditionally2方法,此方法内部构建LoggingEvent对象,然后利用我们前面介绍的通知方法callAllAppener方法,将此LoggingEvent对象分发出去。
void Category::debug(const std::string& message) throw()
void Category::debug(const std::string& message) throw() { // 先检测当前cateogry权限是否支持debug操作 if (isPriorityEnabled(Priority::DEBUG)) _logUnconditionally2(Priority::DEBUG, message); //直接调用_logUnconditionally2来构建LoggingEvent并且将之分发给 //各个appender对象 }
和使用支持格式化字符串和可变参数的版本相比,少了转换为字符串的过程,而是直接调用_logUnconditionally2来进行 组装日志对象和分发日志对象。
debugStream()
class Category { public: ... // 获取的是输出debug信息的流对象,此流输出的所有消息都是debug消息 // 我们可以向这样来使用流 // Category &root = Category::getRoot(); // ... 进行一些设置 // CategoryStream debugStream = root.debugStream(); // debugStream << 3 << "is greater than" << 4 << eol; inline CategoryStream debugStream() { return getStream(Priority::DEBUG); } }; // 内部直接返回一个CategoryStream对象 CategoryStream Category::getStream(Priority::Value priority) { return CategoryStream(*this, isPriorityEnabled(priority) ? priority : Priority::NOTSET); //我们暂时先放一放,下面看一下CategoryStream的实现
我们可以看到debugStream返回的是一个CategoryStream的对象,我们可以看一下CategoryStream的实现
CategoryStream
这个类是一个支持流风格的流,但是它们并没有继承任何已有的流对象,它的内部实现是使用聚合一个字符串流来实现的。这个类算是一个输出流,它的输出目标是某个具体的category对象。
我们先来看一下它的数据成员
class CategoryStream { private: Category& _category; // 所关联的category对象 Priority::Value _priority; //日志的等级 union { std::ostringstream* _buffer; #if LOG4CPP_HAS_WCHAR_T != 0 // 对于这儿的设计我真的是不敢恭维,等到后面看他的算法实现就知道 std::wostringstream* _wbuffer; #endif }; // 作为缓冲区 public: CategoryStream(Category& category, Priority::Value priority); } CategoryStream::CategoryStream(Category& category, Priority::Value priority) : _category(category), _priority(priority), _buffer(NULL) { }
_cateogory就是最后要输出的目的地,_priority是进行输出操作的等级,_buffer是作为CategoryStream的缓存区而存在的。
CategoryStream::operator<<
class CategoryStream { public: template<typename T> CategoryStream& operator<<(const T& t) { if (getPriority() != Priority::NOTSET) { //只有当从构造器中传来的priority不是Priority::NOTSET的时候,它才会进行真正的日志输出操作。 if (!_buffer) { // 首先要检查输入流缓冲区是否存在,不存在的话就创建,然后将输出输出到这个缓冲区中 if (!(_buffer = new std::ostringstream)) { // XXX help help help } } (*_buffer) << t; } return *this; } // 居然还没有这个实现,不是已经有模板成员方法了吗?(实现逻辑和上面的基本一致) CategoryStream& operator<<(const char* t); // 这个方法真搞笑,内部根本就没有用到类型T,为什么要在头上加个T(而且函数根本就没有偏特例化这个概念) // 并且我经过实测,对于vs2013上来讲,这个方法是永远也调用不到, // 即使我的输出对象是std::string方法,感觉有点误人子弟啊,有木有。我不知道log4cpp对于编译器兼容考虑还是什么原因 // 总之下面的方法根本就不应该存在 template<typename T> CategoryStream& operator<<(const std::string& t) { if (getPriority() != Priority::NOTSET) { if (!_buffer) { if (!(_buffer = new std::ostringstream)) { // XXX help help help } } (*_buffer) << t; } return *this; } // 同上,内部方法永远无法执行,可能vs2013内置C++编译器看到这种写法,就直接将这个模板成员方法给剔除掉了 #if LOG4CPP_HAS_WCHAR_T != 0 template<typename T> CategoryStream& operator<<(const std::wstring& t) { if (getPriority() != Priority::NOTSET) { if (!_wbuffer) { if (!(_wbuffer = new std::wostringstream)) { // XXX help help help } } (*_wbuffer) << t; } return *this; } #endif }
我们可以看到,第一个目标方法,已经完成了对各种常用类型的流支持,但是它不支持宽字符类型(不相信的朋友可以试一下, 你先传入一个L"123"到oeprator<<是可以编译通过的,但是输出的内容不是你想要的内容,然后你在传入一个wstring(L"123")到operator<< 中,编译器根本就无法编译成功)。到目前为止,它只是将内容输出到内部的_buffer中,并没有将内容输出到内部的category对象。
而且,在CategoryStream& operator<<(const T& t)模板方法中,会检测_priority属性是否为Priority::NOTSET, 只有当_priority不等于Priority::NOTSET的时候,才会将内容输出到缓冲区中,否则是不进行任何操作的。再次申明这里是非常重要的,明白这儿就会明白Category::debugStream()方法的返回的是CategoryStream(isPriorityEnabled(Priority::DEBUG) ? priority : Priority::NOTSET);当权限不支持debug操作时,这个流是无法进行任何输出操作的
CategoryStream::flush方法
class CategoryStream { public: void flush(); } void CategoryStream::flush() { if (_buffer) { // 缓冲区存在内容的话,他就会将缓冲区的内容通过_category.log方法输出到_category中 getCategory().log(getPriority(), _buffer->str()); //getCategory()获取的就是通过构造器传进来的_category对象 delete _buffer; _buffer = NULL; } }
我们可以看到,categoryStream的flush操作,就是将缓冲区的内容通过_category.log方法输出到_categroy对象中。其实Cateogory::log方法 和我们上面介绍的debug方法功能是类似的,debug方法只能输出Priority::DEBUG权限的消息,而logg方法则比debug方法都一个功能,那么就是它可以指定一个输出权限。
我们来看一下category::log方法的实现
// 多了一个参数,用来设置输出权限等级,内部实现和debug的内部实现基本一致 void Category::log(Priority::Value priority, const std::string& message) throw() { if (isPriorityEnabled(priority)) _logUnconditionally2(priority, message); }
CategoryStream流控制函数的支持
到目前为止,已经讲述了CategoryStream的数据成员,构造函数,以及operator<<和flush,另外Category还为我们提供了几个 流控制符函数,通过这些函数,我们可以知道向std::cout << std::endl, std::cout << std::flush,实现类似这种风格的 原理,我们来看一下这几个函数。
class CategoryStream { public: typedef CategoryStream& (*cspf) (CategoryStream&); //定义了一种函数类型 CategoryStream& operator << (cspf); //关键是这个方法,他让我们支持categoryStream << eol; 这样的写法 std::streamsize width(std::streamsize wide ); // 用来设置每次流输出的宽度,内部是调用的_buffer->width方法来实现的 // 声明了两个友元函数 friend CategoryStream& eol (CategoryStream& os); friend CategoryStream& left (CategoryStream& os); } // 内部直接就是将CategoryStream对象交给pf处理,并且返回其值 CategoryStream& CategoryStream::operator<< (cspf pf) { return (*pf)(*this); } // 执行的功能等价于std::flush,会刷新缓冲区中的内容到Category对象中 CategoryStream& eol (CategoryStream& os) { if (os._buffer) { //缓冲区中有内容则要缓冲区内容进行刷出 os.flush(); } return os; } // 用来设置缓冲区的对齐方式,内部就是通过std::ostringstream的setf标志来实现 // 当指定的宽度大于实际输出值的宽度的时候,他会自动将内容对齐到左面,右边会留空的 CategoryStream& left (CategoryStream& os) { if (os._buffer) { os._buffer->setf(std::ios::left); } return os; } // 用来设置每次流输出的宽度,内部是调用的_buffer->width方法来实现的 std::streamsize CategoryStream::width(std::streamsize wide ) { if (getPriority() != Priority::NOTSET) { if (!_buffer) { if (!(_buffer = new std::ostringstream)) { // XXX help help help } } } return _buffer->width(wide); }
好了到目前为止,CategoryStream的流控制相关方法,只剩下两个get方法,只是用来获取内部属性,在此就不在啰嗦了。
现在回过头来再看一下Category:;getStream方法实现的getStream()。
CategoryStream Category::getStream(Priority::Value priority) { return CategoryStream(*this, isPriorityEnabled(priority) ? priority : Priority::NOTSET); //关键就是第二个参数的设置,当priority不在category对象的权限范围内的时候,他会将第二个参数设置为Priority::NOTSET }
正如我们源码注释讲的那样,当priority不被当前Category对象支持的时候,他会将新建的CategoryStream的_priority属性设置为 Priority::NOTSET,原因,前面在介绍CategoryStream::operator<<(const T&)这个方法时候已经介绍过,这儿再次重复一下:
template<typename T> CategoryStream& CategoryStream::operator<<(const T& t) { // 只有当从构造器中传过来的priority不是Priority::NOTSET的时候,它才会进行真正的日志输出操作 if (getPriority() != Priority::NOTSET) { if (!_buffer) { if (!(_buffer = new std::ostringstream)) { // XXX help help help } } (*_buffer) << t; } return *this; }
只有当CategoryStream的_priority属性不是Priority::NOTSET的时候,它才会将输出流中的内容输出到缓冲区中,否则它是什么也不干的。也就是说上面的getStream(Priority::Value priority)对象返回的流有两种,一种是可以进行正常输出的流,一种是不能进行正常输出的流。
总结
- 首先我们讲解了category和appener之间的关系,这里采用了观察者设计模式,并且介绍了相应的注册/反注册方法( addAppender(Appender*), addAppender(Appender&)),和对应的通知方法(callAppenders)。
- 对于添加方法,它内部使用了两个容器来维护内部添加进来的Appender对象,一个AppenderSet _appender用来保存所有添加进来的Appender对象,一个_ownsAppender用来记录某个对象是否被完全拥有。当删除某个Appender对象,获取移除所有Appender对象的时候,,_ownsAppender可以用来 检测某个Apepnder是否能够使用delete运算符来删除掉。
- callAppenders调用每个Appender对象的doAppend方法来讲LoggingEvent对象通知给它们,并且根据_additivity属性来决定是否将此事件传递给它的父类。
- 然后我们分析了Category的结构。Category有一个成员Category *_parent,这个参数是通过构造函数传递进来的。另外,在callAppends方法中,除了通知所有已添加的Appender对象,还会根据_additvity的值来决定是否调用_parent.callAllAppenders方法,父类同样此逻辑。这是典型的责任链模式。
- 再之后我们学习了Category对象的工程方法getRoot()方法和getInstance()方法。它们内部其实调用的是HierachyMaintainer的 getInstance方法,然后我们详细介绍了这个HierachyMaintainer类,他实际上就是Category的工厂类,加上维护类,我们通过阅读源码知道, HierachyMaintainer的getInstance的方法首先是线程安全,并且将传递的名称为"",返回的是根Category,当不为空的时候,我们可以 传递xx.xx.xx来创建一个Category对象,HierachyMaintainer工厂方法会自动构建此链上的相关对象并且将最终对象返回,注意返回的对象的名称 仍然是xx.xx.xx,通过读源码,我们会发现,这个工厂类的getInstance()方法是线程安全的。
- 然后我们介绍了Category对象的shutdown方法,这个类内部调用的仍然是HierachyMaintainer类的shutdown方法,在此方法内部执行如下逻辑, 首先调用所有已经创建好的Category对象的removeAllAppenders方法,然后调用内部注册的好多回调函数(当有异常发生,那么后面的回调函数 都不会被执行)。至于这个利用回调函数来进行额外进行某些操作的特性,在整个Category设计中并没有被使用到。还有呢,在HierachyMaintainer 的析构函数中,会先调用shutdown方法,然后调用removeAllCategories方法,前者已经介绍过,后者,内部实现就是delete所有已经实例化的Category 对象,避免内存发生泄漏。
- 对于HierachyMaintainer,这个类实际被Category使用的对象只有一个,Category获取 HierachyMaintainer对象是通过HierachyMaintainer::getDefaultMaintainer方法来实现。这是个HierachyMaintainer类,并没有对构造器 进行私有化,所以我们认为可能HierachyMaintainer是非严格意义上的单例设计,因为他提供了一个单例的工厂方法。
- 下面我们选择某一个具体的日志操作来介绍,我们选择的是debug操作,我们讲解了Category类对于某些级别的检测方法,isPriorityEnabled方法, 内部是检测_priority是否大于函数传进来的优先级。然后我们介绍了debug的几个重载方法,和debugStream方法,debug两个重载方法执行逻辑 都是一致的,当有格式化字符串和可变参数的时候,他们会构造可变参数然后调用_logUnconditionally方法,此方法内部会利用StringUtil::vform 来将格式化字符串和可变参数转换为字符串。然后调用_logUnconditionally2来进行真正的日志操作,此方法会利用一些信息来构建LoggingEvent对象,然后调用我们上面介绍的通知方法callAppenders来讲LoggingEvent对象通知给已经注册进来的所有的Apender对象,并且根据_additivity属性来 确定是否将此事件传递给父Category对象
- 最后我们讲解了debugStream方法的实现,内部调用的是getStream方法,而getStream方法返回的是一个CategoryStream对象,并且根据是否支持对应的priority操作来将它第二个参数设置为Priority::NOTSET。
- 对于CategoryStream,内部其实是聚合了一个std::ostringstream对象,它是将这个对象作为缓冲区,并且提供了一个支持常用类型的模板方法operator<<来实现对于大多数类型的输出流支持,并且对于 这个模板方法,我们也详细说明了那儿在输出之前他会对内部的_priority属性进行判断,如果只为Priority::NOTSET则不进行任何操作。这儿也就是 Category::getStream那个方法那儿里面那样实现的原因。CategoryStream还为我们提供了控制流输出的函数,比如eol, 比如left。总之当我们不想 使用格式化字符串 + 可变参数的时候,可以尝试使用这个方法。
这篇关于log4cpp源码阅读:Category组件学习的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-15SendGrid 的 Go 客户端库怎么实现同时向多个邮箱发送邮件?-icode9专业技术文章分享
- 2024-11-15SendGrid 的 Go 客户端库怎么设置header 和 标签tag 呢?-icode9专业技术文章分享
- 2024-11-12Cargo deny安装指路
- 2024-11-02MongoDB项目实战:从入门到初级应用
- 2024-11-01随时随地一键转录,Google Cloud 新模型 Chirp 2 让语音识别更上一层楼
- 2024-10-25Google Cloud动手实验详解:如何在Cloud Run上开发无服务器应用
- 2024-10-24AI ?先驱齐聚 BAAI 2024,发布大规模语言、多模态、具身、生物计算以及 FlagOpen 2.0 等 AI 模型创新成果。
- 2024-10-20goland工具下,如修改一个项目的标准库SDK的版本-icode9专业技术文章分享
- 2024-10-17Go学习:初学者的简单教程
- 2024-10-17Go学习:新手入门完全指南