log4cpp源码阅读:Category组件学习

Category组件在Log4Cpp库中,是真正的Logging对象。我们可以使用category对象来进行各种级别的日志操作,比如debug、warn、info…并且这个对象还可以添加各种appender,用来将日志指令分发给各个appender对象来输出到最终目的地上

Category与Appender对象之间的关系(观察者模式)

category与appender之间是一种目标和观察者关系。也就是说它们之间使用的设计模式就是观察者设计模式:category对象会将具体的debug、warn、info…指令分发给内部添加的append对象。

log4cpp源码阅读:Category组件学习

添加/移除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自身的结构(责任链模式)

log4cpp源码阅读: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的工厂方法(工厂方法)

log4cpp源码阅读: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。总之当我们不想 使用格式化字符串 + 可变参数的时候,可以尝试使用这个方法。
上一篇:webrtc源码分析 pacer代码流程


下一篇:Ubuntu 下使用Remmina Remote Desktop client 连接windows server输入法的问题