log4cpp source code reading: Category component learning

Posted by MasterACE14 on Wed, 02 Mar 2022 04:23:52 +0100

The category component is a real Logging object in the Log4Cpp library. We can use the category object to perform various levels of log operations, such as debug, warn, info... And this object can also add various Appenders to distribute log instructions to various appender objects to output to the final destination

Relationship between Category and Appender object (observer mode)

The relationship between category and appender is a relationship between target and observer. In other words, the design pattern used between them is the observer design pattern: the category object will distribute the specific debug, warn, info... Instructions to the internally added append object.

Add / remove Appender objects

	class Category {
	private:
	        typedef std::map<Appender *, bool> OwnsAppenderMap;
	        
	        AppenderSet _appender;  //The specific types of AppenderSet are: typedef STD:: set < appender * > AppenderSet; In appender HH is defined in the file
	        OwnsAppenderMap _ownsAppender; // It is used to copy the auxiliary information that the Appender object added inside the implementation can be safely delet ed
	        
	        mutable threading::Mutex _appenderSetMutex;  //Lock: a means by which auxiliary data can be accessed safely
	       
	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();
	}


// Add the specified appender object to the internal management container. If the parameter is invalid, STD:: invalid will be thrown_ Argument exception
void Category::addAppender(Appender* appender) {
        if (appender) {
            threading::ScopedLock lock(_appenderSetMutex);
            { 
            	// First, check whether the specified appender already exists. It will be added only when it does not exist. Otherwise, you must not do anything
                // Because the pointer is passed. If it is the same, it means that it is the same thing at all
                AppenderSet::iterator i = _appender.find(appender);
                if (_appender.end() == i) {
                    // not found
                    _appender.insert(appender); // Perform a real add operation
                    _ownsAppender[appender] = true;
                }
            }
        } else {
            throw std::invalid_argument("NULL appender");  // Note that the second parameter is set to true (when the category class performs the removal operation, you will know its purpose)
        }
    }
    
     // Another overloaded appender method
    // The executed logic is basically similar to that of addAppender(Appender *). Because appender is a reference, it must be an existing object, so null value detection is not required
    // It also searches the existing list. If it does not exist, it will add new content
    // The only difference is (note here): when it is found that the appender is an appender that has not been added, in addition to inserting the appender into the_ Appender is easy to use, but also
    // _ ownsAppender[&appender] = false;  Note that this is set to false, while the above version is set to 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();  //Completely delete the information in the two containers used to record the information of the appender
            _appender.clear();           
        }
    }

	// Remove current appender from Category
	void Category::removeAppender(Appender* appender) {
        threading::ScopedLock lock(_appenderSetMutex);
        {
            AppenderSet::iterator i = _appender.find(appender);  //Find first
            if (_appender.end() != i) {            
                OwnsAppenderMap::iterator i2;
                  // Used to check whether the current category object completely belongs to the appender
                // If so, the appender will be completely deleted, and then it will also be deleted_ The existence of this map object in ownsAppender is also removed
                if (ownsAppender(*i, i2)) {  
                    _ownsAppender.erase(i2);
                    delete (*i);
                }
                _appender.erase(i);       // Whether it belongs to or not, it will certainly remove this object from_ Remove from appender
            } else {
                // appender not found 
            }
        }
    }


 // Check whether the current category object fully owns the appender object (only if it fully owns it can it be deleted by using 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;
    }

The methods of target registration / de registration of observers are summarized as follows:

  • Category actually uses appenderset_ Appender to maintain the added appender object
  • Category also uses ownsappendermap_ Ownsappender to assist the remove method, which can completely release all the added Appender s
  • The Appender method in Category is similar to the register method of subject in observer mode, while the remove method is similar to the Unregister method in subject. You can see that Category is completely acting as a subject

callAppenders method (notification method in observer mode)

 void Category::callAppenders(const LoggingEvent& event) throw() {
        threading::ScopedLock lock(_appenderSetMutex);
        {
           // Traverse each appender object one by one and call the doAppend method of the appender object
            if (!_appender.empty()) {
                for(AppenderSet::const_iterator i = _appender.begin();
                    i != _appender.end(); i++) {
                    (*i)->doAppend(event);
                }
            }
        }
  // You don't need to read the following code for the time being. You'll understand it when you finish learning the structure of Category
        if (getAdditivity() && (getParent() != NULL)) {
            getParent()->callAppenders(event);
        }
    }

We can see that the Category::callAppender method is similar to the notify method of Subject in the Observer mode, and the Appender::doAppend method is similar to the receive method of Observer in the Observer mode.

Category's own structure (responsibility chain mode)


Source code

	class Category {
	protected:
	     // The only constructor that can be called by subclasses or friends
	     // Name is the name of the current category object
	     // Parent will finally be set to_ In the parent attribute (he is the only one we need to care about so far)
		// Priority indicates the lowest priority that the log can handle. If it is lower than this priority, the log operation will not be carried out
		 Category(const std::string& name, Category* parent, 
	                                Priority::Value priority = Priority::NOTSET);
	private:
		 Category(const Category& other);  //Copy and assignment prohibited
	     Category& operator=(const Category& other);
	
		Category* _parent;  // Execute parent category
	
		 const std::string _name;  //Represents the name of the category object
		 volatile Priority::Value _priority;  //The code is the lowest priority that this object can handle log operations
		 volatile bool _isAdditive;  //We don't need to consider this attribute for the time being
	}


	Category::Category(const std::string& name, Category* parent, Priority::Value priority) : 
	        _name(name),
	        _parent(parent),
	        _priority(priority),
	        _isAdditive(true) {
	    }

It can be seen from the above that Category is a chain result. A Category object can also point to a parent Category object, and then the parent Category object can also have its own parent Category. By analogy, a long Category chain can be formed.

You can also see that Category has a unique constructor, and this constructor is protected. So how do we instantiate it? Do we want to inherit this Category class? In fact, the Category object also provides several factory methods.

Instead of thinking about these factory methods, let's take a look at the attributes of Category:

  • Name is the name of Category type
  • _ priority is essentially an int type. It is used for specific log operations of Category class
  • _ isAdditive, let's take a specific look. Because when we talked about the category:: callattachments method, we didn't mention a piece of code. Now we have completed all the necessary foundations. Let's look back:
	void Category::callAppenders(const LoggingEvent& event) throw() {
	    threading::ScopedLock lock(_appenderSetMutex);
	    {
	        // Traverse each appender object one by one and call the doAppend method of the appender object
	        . . . . . .   
	    }
	
	  	// getAdditivity() is_ Access method of isAdditivity property,
	    // getParent() gets_ parent attribute
	    // We can see that the following logic is, if_ If isAdditivity==true, the
	    // The father's callAppenders method, but the father is also a Category object. He will still
	    // Call the doAppend method to which you have added an Appender object, and then detect your own_ isAdditivity to decide whether to set this LoggingEvent
	    // Continue upward transfer processing
	    if (getAdditivity() && (getParent() != NULL)) {
	        getParent()->callAppenders(event);
	    }
	}

From here, we can see that the category class also uses the responsibility chain mode: when constructing, it will pass in the parent category object. When executing the callAppenders method, it can_ The setting of isAdditivity property passes event to its parent category object.

So far, the observer mode part of the whole Category has been thoroughly explained.

Factory method of Category (factory method)


Let's first look at the source code of Category::getRoot method and Category::getInstance method

	Category& Category::getRoot() {
	    return getInstance("");
	}
	
	
	Category& Category::getInstance(const std::string& name) {
	    return HierarchyMaintainer::getDefaultMaintainer().getInstance(name);
	}

We can see that the internal call of getRoot static method is getInstance static method, while the internal call of getInstance static method is hierarchymaintainer:: getdefaultmaintainer() getInstance(name)

Also:

	class Category {
	    friend class HierarchyMaintainer;
	    . . . 
	};

That is, HierarchyMaintainer is a friend class of Category. We think again that Category provides a construction method with unique implementation, which is protected. It can be inferred that HierarchyMaintainer is a factory class of Category.

HierarchyMaintainer

The factory class of Category is internally responsible for the construction of all Category objects.

Let's take a look at its data members first

	class HierarchyMaintainer {
	public:
		typedef std::map<std::string, Category*> CategoryMap;
	protected:
		CategoryMap _categoryMap;
	}

Contains a member inside_ categoryMap. All Category objects created inside the HierarchyMaintainer are saved in the_ In the categorymap container, it is called internally through the Category::getInstance method.

The following is HierarchyMaintainer:: getdefaultmaintainer() getInstance(name); For reference, let's take a look at the functions of the entire HierarchyMaintainer class

HierarchyMaintainer::getDefaultMaintainer() method (an implementation of a singleton factory method)

	class HierarchyMaintainer {
	public:
		 static HierarchyMaintainer& getDefaultMaintainer();
	}

	// This method is static
	// When this method is called for the first time, defaultMaintainer will be constructed and returned
	// The next time this method is called, the static object will be returned directly	
	// This is a typical implementation of singleton mode
	HierarchyMaintainer& HierarchyMaintainer::getDefaultMaintainer() {
        static HierarchyMaintainer defaultMaintainer;
        return defaultMaintainer;
    }

As you can see, getdefaultmaintainer () is actually a singleton factory method of the HierarchyMaintainer class. So HierarchyMaintainer:: getDefaultMaintainer() getInstance(name); The internal real getInstance method is the HierarchyMaintainer::getInstance method

HierarchyMaintainer::getInstance method

	class HierarchyMaintainer {
	public:
		virtual Category& getInstance(const std::string& name);
	}

	 Category& HierarchyMaintainer::getInstance(const std::string& name) {
        threading::ScopedLock lock(_categoryMutex);  // Ensure that getInstance() can be accessed safely by multiple threads
        return _getInstance(name); //The real call is_ getInstance method
    }

This method is actually a wrapper method. It is thread safe and internally called_ getInstance method.

  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;
         // This is to detect whether the category object with the specified name has been created. If it is created, it will be returned (that is, use the find method of _categoryMap)
        result = _getExistingInstance(name);
        
        if (NULL == result) {   // The category object with the specified name has not been created yet. You need to create one      
        	// If the name is' ', it means that root is created    
            if (name == "") {   
                // The second parameter is NULL, indicating that there is no parent category pointer (the root has no parent category)
                // That is, the implementation of Category::getRoot method is: hierarchymaintainer:: getdefaultmaintainer() getInstance("");
                result = new Category(name, NULL, Priority::INFO);
            } else {
              // The format of name can contain multiple points:
              // For example, parentparent parent. son. sonson
               std::string parentName;
                size_t dotIndex = name.find_last_of('.');   //Find the last one first Location of
                if (name.length() <= dotIndex) { // Why is it less than or equal to here? Because when name doesn't find '.' The returned value is std::size_t(-1)
                    parentName = ""; // This value is size_t is the largest value in unsigned int, which is no bigger than it
                } else {
                    parentName = name.substr(0, dotIndex); //The name of the last point of the package is not. For example, name is parent parent. son. Sonson, then the parentName here is parentparent parent. son
                }
                Category& parent = _getInstance(parentName); // Calling recursively is equivalent to creating the parent category first
                result = new Category(name, &parent, Priority::NOTSET); // Then use the parent category of the creation number to create the child category
            }	  
            _categoryMap[name] = result; //The category object of the final creation number will be saved to_ In the categoryMap attribute, take the name of the category object as the key
        }
        return *result;
    }

This method is the core of the whole plant method and mainly implements the following processes:

  • Check whether name has created a number. If it has, return it
  • Check whether the name is empty. If so, create a root node (for one detail, the priority attribute of the root node is set to Priority::INFO by default), and save the new node to_ In the categoryMap property
  • 3. If name is not empty, it will be called recursively first_ The getInstance method obtains the parent node specified by the name (for example, if the name is parent.son, he will use _getInstance("parent") to create the parent node), and then uses the parent category object of the creation number to create a new child category, that is, the category object of the specified name. (there is also a detail here. The priority attribute of the non root node is set to Priority::NOTSET), and it is added to the_ In the category map (with name as the key and the newly created category pointer as the value)
  • In the recursive call above, for example, name = parent Son, then it will call_ getInstance("parent"), and then this method will be called internally_ getInstance(""), this method returns the root node as root, and then continues to return to_ In getInstance("parent"), create a new Category("parent", root, Priority::NOTSET), assuming p, then return this object and continue to return_ getInstance("parent.son"), then Category("parent.son", p, Priority:NOTSET) will be returned

According to this_ We can know the implementation of getInstance method:

  • First, the specified name can contain several '
  • Default for root category object_ The priority attribute is Priority::INFO, and other non Root Category objects_ Priority attribute Priority::NOTSET attribute.
  • All created Category objects are backed up to HieratchyMaintainer_ In the categoryMap property

Hierarchymaintainer:: ~ decomposition method of hierarchymaintainer

	class HierarchyMaintainer {
	    public:
	        virtual ~HierarchyMaintainer();
	}

	 HierarchyMaintainer::~HierarchyMaintainer() {
        shutdown();
        deleteAllCategories();
    }

We can see that the shutdown() method and the deleteAllCategories() method are executed.

HierarchyMaintainer::shutdown method

Let's take a look at the shutdown() method, but before we look at this method, we need to look at the preparation:

	class HierarchyMaintainer {
	public:
		typedef void (*shutdown_fun_ptr)();  //It defines a function pointer type without parameters and return value, which is actually a type callback function
		void register_shutdown_handler(shutdown_fun_ptr handler); //Register the specified callback function into handlers
	private:
	    typedef std::vector<shutdown_fun_ptr> handlers_t; //The type of container used to hold the function pointer
		handlers_t handlers_;
	}


	//Register the specified callback function into handlers
	void HierarchyMaintainer::register_shutdown_handler(shutdown_fun_ptr handler)
    {
        handlers_.push_back(handler); 
    }

That is, the HierarchyMaintainer body provides callback functions that can be extended. As for the specific functions of these callback functions, we need to take a look at the shutdown method of this class.

	class HierarchyMaintainer {
	public:
		virtual void shutdown();
	}



	void HierarchyMaintainer::shutdown() {
        threading::ScopedLock lock(_categoryMutex);   //Ensure that this method is thread safe
        {
           // Clear the Appender objects inside all instantiated Category objects (by calling the removeAllAppenders method of Category, which we discussed earlier)
            for(CategoryMap::const_iterator i = _categoryMap.begin(); i != _categoryMap.end(); i++) {
                ((*i).second)->removeAllAppenders();
            }
        }
        
         // Call use register_ shutdown_ Callback function added by handler
   		 // In fact, you can see that it is also limited, because when a callback function occurs, other callback functions will not have a chance to be executed
  		  // I don't know whether the setter is intentional or unintentional. Personally, I think we can try this catch... The block is added to the inside of the for loop
       try
        {
           for(handlers_t::const_iterator i = handlers_.begin(), last = handlers_.end(); i != last; ++i)
              (**i)();
        }
        catch(...)
        {
        }
        
    }

Briefly summarize the functions of the HierarchyMaintainer::shutdown method:

  • Clear the added appener inside all category objects with created numbers
  • Call all registered callback functions. It should be noted that this callback function cannot have exceptions, otherwise the callback parameters registered behind this callback function may be terminated

HierarchyMaintainer::deleteAllCategories method

	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();
        }
    }

This method is very simple. delete it directly_ All members within the categoryMap, so that memory leakage will not occur

The getInstance() method and destructor method described above are the core functions of the whole class, that is, they are used to create Category objects and maintain already created Category objects.

Summary:

  • The factory method getInstance() of the HierarchyMaintainer method, the shutdown() method to clean up each category object, and the deleteAllCategories() method to release all internally created category objects are thread safe
  • When the HierarchyMaintainer object is destroyed, it will automatically call the shutdown() method and the deleteAllCategories() method., Therefore, for the whole program, even if the program does not call the Category::shutdown() method in the end, no resource data will be generated. That is to say, this factory class is responsible for maintaining the life cycle of all category objects with created numbers.
  • 3. For the category object with father created by getInstance method, their name is the name containing a dot, not the name after the last dot. We can find the reason from getInstance().

Other methods:

	// Is there a category for the name object
    Category* HierarchyMaintainer::getExistingInstance(const std::string& name) {
        threading::ScopedLock lock(_categoryMutex);
        return _getExistingInstance(name);
    }


		// All categories that have been created
	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()

Resource recovery method of Category

	class Category {
        public:
			 static void shutdown();
	}

	void Category::shutdown() {
        HierarchyMaintainer::getDefaultMaintainer().shutdown();
    }

The shutdown method of the Category object actually calls the hierarchymaintainer Shutdown method. The internal execution steps can be briefly recalled:

  • Call the removeAllAppenders method of all categories to delete all the owned appender objects and empty the inner part of the category object_ The content in the appender container, and the content in the ownsAppender container
  • Call the callback function registered inside HierarchyMaintainer. (in fact, for this step, by reading the whole implementation of Category, he did not register any callback function, that is, this extended cleaning mechanism was not used in practical use.)
  • Add: all created category objects have not been destroyed. It is destroyed when the static variable defined inside the HierarchyMaintainer::getDefaultMaintainer() method is destroyed. All Appender s that are fully owned by the category object are destroyed here.

Take a look at the deconstruction method of Category object:

	class Category {
	public:
	    ~Category();
	    ......
	};

	~Category::Category() {removeAllAppenders();}

Is to remove all internal Appender objects. It is reasonable to consider the existence of this method alone, but because the life cycle of the category object is completely maintained by the HierachyMaintainer object, the shutdown method of HierachyMaintainer has the function of this method, and the destructor method of HierachyMaintainer also has this function, so I think, Either do not do any processing in the destructor method of this class, or do not provide the process of calling removeAllAppenders of each category object with respect to the HierachyMaintainer::shutdown method. I think the former is more reasonable, that is, it does not carry out any processing in the deconstruction method of category.

Log method of Category

Category can be used to output various logs, and it also handles a demand. For example, in some cases, I want to output various types of information, including error information, general information and debugging information, but in some cases, we only want to see error information. Category uses property:: value_ Priority attribute.

The Priority class has a PriorityLevel enumeration type inside, which defines various enumeration constants and a typedef int Value; Two tool functions are also defined. One is getPriorityName, which can convert Value type to string type. For example, getPriorityName(Priority::DEBUG) returns "DEBUG". The other is getprioritvalue, which converts string to Value. For example, getprioritvalue ("DEBUG") outputs Priority::DEBUG. This method string can also be a number, For example, getPriorityValue("123") returns 123, which is generally not used in this way.

The auxiliary method used to determine whether a log level operation is performed is isPriorityEnabled. The source code is as follows:

	bool Category::isPriorityEnabled(Priority::Value priority) const throw() {
	    return(getChainedPriority() >= priority);   // Only when set_ true will be returned only when the priority value is greater than the specified priority value
	}

	// The implementation of getChainedPriority method above
	Priority::Value Category::getChainedPriority() const throw() {
	    // REQUIRE(rootCategory->getPriority() !=  Priority:: notset), the conditions here must be true,
	    //Because the root object uses HierachyMaintainer::getInstance(""), when the internal name is empty, the priority value of the root created by him is Priority::INFO
	    //As long as the category of the following program is not set to root::, it will not be set to priority
	
	    // Child - > parent - > parent... Find first_ The priority attribute is not set to Priority::NOTSET because the default value of root is_ Priority is Priority::INFO
	    // This cycle can definitely be terminated.
	    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");
        }
    }

As we can see, when the internal setting_ When the priority value is greater than the specified priority value, it means that all operations are satisfied.

Let's take a look at a specific logging method. We choose the debug method. For other info, alert, warn... Their composition is the same. We can take a look at their similar structure:

	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);
        }
        
	}
	. . . . 

The following only introduces the system in the way of debug.

void debug(const char* stringFormat, ...)

void Category::debug(const char* stringFormat, ...) throw() {
    // First detect permissions
    if (isPriorityEnabled(Priority::DEBUG)) {
        va_list va;
        va_start(va,stringFormat);
        _logUnconditionally(Priority::DEBUG, stringFormat, va); //Real log operation
        va_end(va);
    }
}   

You can see that this class will first check whether the current permission supports Priority::DEBUG, as long as_ Priority > Priority::DEBUG is supported. Then initialize the variable parameters for real log operation, and then release the variable parameters. So the key is_ logUnconditionally implementation.

_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() {
        //Assemble a log event object: the name of the current category object; Messages to be written; NDC value of the current thread. If NDC::put is not used, an empty string will be returned; The level of this log operation              
        LoggingEvent event(getName(), message, NDC::get(), priority);
        callAppenders(event);// Call the notification interface described above for this log event object, and distribute this object to each 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);
    }

Summarize the execution flow of void debug(const char* stringFormat...). First, organize variable parameters and then call them. logUnconditionally distributed, and_ logUnconditionally internally uses the StringUtil::vform method to convert variable parameters into strings, and then calls logUnconditionally2 method, which internally constructs the LoggingEvent object, and then uses the notification method callAllAppener method described earlier to distribute the LoggingEvent object.

void Category::debug(const std::string& message) throw()

void Category::debug(const std::string& message) throw() { 
    // First, check whether the current cateogry permission supports the debug operation
    if (isPriorityEnabled(Priority::DEBUG))
        _logUnconditionally2(Priority::DEBUG, message); //Direct call_ logUnconditionally2 to build LoggingEvent and distribute it to
                                                        //Each appender object
}

Compared with the version that supports formatted strings and variable parameters, there is less conversion to strings, but direct calls_ logUnconditionally2 to assemble and distribute log objects.

debugStream()

	class Category {
	public:
	    ...
	
	    // Get the stream object that outputs debug information. All messages output by this stream are debug messages
	    // We can use streams in this way
	    // Category &root = Category::getRoot();
	    //  ...  Make some settings
	    // CategoryStream debugStream = root.debugStream();
	    // debugStream << 3 << "is greater than" << 4 << eol;
	    inline CategoryStream debugStream() {
	        return getStream(Priority::DEBUG);
	    }
	};

// A CategoryStream object is directly returned internally
    CategoryStream Category::getStream(Priority::Value priority) {
        return CategoryStream(*this, isPriorityEnabled(priority) ?
                              priority : Priority::NOTSET); //Let's put it aside for the time being. Let's take a look at the implementation of CategoryStream

We can see that debugStream returns an object of CategoryStream. We can take a look at the implementation of CategoryStream

CategoryStream

This class is a stream that supports stream style, but they do not inherit any existing stream objects. Its internal implementation is implemented by aggregating a string stream. This class is an output stream, and its output target is a specific category object.

Let's take a look at its data members first

	class CategoryStream {
	private:
	        Category& _category;   // The associated category object
	        Priority::Value _priority; //Log level
		    union {
       		 	std::ostringstream* _buffer;
				#if LOG4CPP_HAS_WCHAR_T != 0 / / I really don't dare to compliment the design here. I'll see his algorithm implementation later
				        std::wostringstream* _wbuffer;
				#endif
  		  };                                  // As a buffer

	public:
	        CategoryStream(Category& category, Priority::Value priority);
	}



	CategoryStream::CategoryStream(Category& category, Priority::Value priority) :
        _category(category),
        _priority(priority),
        _buffer(NULL) {
    }

_ Category is the final destination to output_ priority is the level of output operation_ buffer exists as the cache of CategoryStream.

CategoryStream::operator<<

class CategoryStream {
public:
	  template<typename T> 
       CategoryStream& operator<<(const T& t) {
            if (getPriority() != Priority::NOTSET) { //Only when the priority passed from the constructor is not Priority::NOTSET will it perform the real log output operation.
                if (!_buffer) {  // First, check whether the input stream buffer exists, create it if it does not exist, and then output to this buffer
					if (!(_buffer = new std::ostringstream)) {
                        // XXX help help help
                    }
                }
                (*_buffer) << t;
            }
            return *this;
        }

	  // There is no such implementation yet. Isn't there already a template member method? (the implementation logic is basically consistent with the above)
   	 CategoryStream& operator<<(const char* t);


	// This method is really funny. Type T is not used internally at all. Why add a t to the head (and there is no concept of partial specialization of functions at all)
    // And I have measured that for vs2013, this method can never be called,
    // Even if my output object is the std::string method, it feels a little wrong for people's children. I don't know why log4cpp is considered compiler compatibility or why
    // In short, the following methods should not exist at all

		 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;
        }

	// As above, the internal method can never be executed. Maybe vs2013 built-in C + + compiler sees this writing method and directly eliminates the template member method
	    #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


}

We can see that the first target method has completed the flow support for various common types, but it does not support wide character types (if you don't believe it, you can try it. You can compile by passing in an L"123" to oeperator < < first, but the output content is not what you want, and then you pass in a wstring(L"123") to operator < <, The compiler cannot compile successfully at all). So far, it's just outputting content internally_ In the buffer, the content is not output to the internal category object.

Moreover, in the categorystream & operator < < (const T & T) template method, it will detect_ Whether the priority attribute is priority:: notset or not, only when_ When priority is not equal to priority:: notset, the content will be output to the buffer. Otherwise, no operation will be performed. Once again, it is very important to declare here. If you understand here, you will understand that the Category::debugStream() method returns the category stream (ispriorityenabled (priority:: debug)? priority : Priority::NOTSET); When the permission does not support the debug operation, the stream cannot perform any output operation

CategoryStream::flush method

	class CategoryStream {
	public:
		void flush();
	}


	void CategoryStream::flush() {
        if (_buffer) {  // If there is content in the buffer, it will pass the content of the buffer_ category.log method output to_ In category
            getCategory().log(getPriority(), _buffer->str());   //What getCategory() gets is passed in through the constructor_ category object
            delete _buffer;
            _buffer = NULL;
        }
    }

We can see that the flush operation of categoryStream is to pass the contents of the buffer through_ category.log method output to_ In the category object. In fact, the function of the category:: log method is similar to that of the debug method described above. The debug method can only output messages with Priority::DEBUG permission, while the log method has a function than the debug method, so it can specify an output permission.

Let's take a look at the implementation of the category::log method

	 // An additional parameter is used to set the output permission level. The internal implementation is basically the same as that of debug
	void Category::log(Priority::Value priority, 
                       const std::string& message) throw() { 
        if (isPriorityEnabled(priority))
            _logUnconditionally2(priority, message);
    }

Support for CategoryStream flow control functions

So far, we have talked about the data members, constructors, operators < < and flush of CategoryStream. In addition, Category also provides us with several flow controller functions. Through these functions, we can know how to realize the principle of similar style to STD:: cout < < STD:: endl, STD:: cout < < STD:: flush. Let's take a look at these functions.

	class CategoryStream {
	public:
		   typedef CategoryStream& (*cspf) (CategoryStream&);  //Defines a function type
		 
		   CategoryStream& operator << (cspf);   //The key is this method. It allows us to support categorystream < < EOL; This way of writing

		    std::streamsize width(std::streamsize wide );   // It is used to set the width of each stream output. It is called internally_ Buffer - > width method

		   // Two friend functions are declared
	       friend CategoryStream& eol (CategoryStream& os);
	       friend CategoryStream& left (CategoryStream& os);	
	}


	// The internal direct is to hand over the CategoryStream object to pf for processing and return its value
	CategoryStream& CategoryStream::operator<< (cspf pf) {
		return (*pf)(*this);
    }

	// The function executed is equivalent to std::flush, which will refresh the contents of the buffer into the Category object
	CategoryStream& eol (CategoryStream& os) {
	    if  (os._buffer) {  //If there are contents in the buffer, the contents of the buffer shall be brushed out
	        os.flush();
	    }
	    return os;
	}

	// It is used to set the alignment of the buffer, which is internally realized through the setf flag of std::ostringstream
	// When the specified width is greater than the width of the actual output value, it will automatically align the content to the left and leave it blank on the right
	CategoryStream& left (CategoryStream& os) { 
	    if  (os._buffer) {
	        os._buffer->setf(std::ios::left);
	    }
	    return os;
	}


	// It is used to set the width of each stream output. It is called internally_ Buffer - > width method
	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); 
	}

Well, so far, there are only two get methods left in the flow control related methods of CategoryStream, which are only used to obtain internal properties. I won't be verbose here.

Now look back at Category:;getStream() implemented by getstream method.

CategoryStream Category::getStream(Priority::Value priority) {
    return CategoryStream(*this, isPriorityEnabled(priority) ?  priority : Priority::NOTSET);   //The key is the setting of the second parameter. When priority is not within the permission range of the category object, it will set the second parameter to Priority::NOTSET
}

As our source code comments say, when priority is not supported by the current Category object, it will update the new Category stream_ The reason why the priority attribute is set to Priority::NOTSET has been described earlier when introducing the method of CategoryStream:: operator < < (const T &), which is repeated here again:

template<typename T> 
CategoryStream& CategoryStream::operator<<(const T& t) {
    // Only when the priority passed from the constructor is not Priority::NOTSET will it perform the real log output operation
    if (getPriority() != Priority::NOTSET) {
        if (!_buffer) {
            if (!(_buffer = new std::ostringstream)) {
                // XXX help help help
            }
        }
        (*_buffer) << t;
    }
    return *this;
}

Only when CategoryStream_ When the priority attribute is not Priority::NOTSET, it will output the content in the output stream to the buffer, otherwise it will do nothing. In other words, the above getStream(Priority::Value priority) object returns two kinds of streams: one is the stream that can be output normally, and the other is the stream that cannot be output normally.

summary

  • Firstly, we explain the relationship between category and appener. Here we adopt the observer design pattern, and introduce the corresponding registration / de registration methods (addappender (appender *), addappender (appender &), and the corresponding notification methods (callAppenders).
    • For the addition method, it uses two containers internally to maintain the appender object added internally, an appenderset_ Appender is used to save all added appender objects, one_ ownsAppender is used to record whether an object is fully owned. When an appender object is deleted and all appender objects are removed_ ownsAppender can be used to detect whether an apepder can be deleted by using the delete operator.
    • callAppenders call the doAppend method of each Appender object to notify them of the LoggingEvent object, and_ The additivity property determines whether to pass this event to its parent class.
  • Then we analyze the structure of category. Category has a member category*_ Parent, this parameter is passed in through the constructor. In addition, in the callAppends method, in addition to notifying all Appender objects that have been added, according to_ The value of additvity to determine whether to call_ parent.callAllAppenders method, and the parent class uses the same logic. This is a typical responsibility chain model.
  • Then we learned the engineering methods of Category object, getRoot() and getInstance(). In fact, what they call internally is the getInstance method of HierachyMaintainer. Then we introduce this HierachyMaintainer class in detail. It is actually the factory class of Category, plus the maintenance class. We know from reading the source code that the getInstance method of HierachyMaintainer is thread safe first, and the name passed is "", and the root Category is returned, When it is not empty, we can pass XX xx. XX to create a Category object. The HierachyMaintainer factory method will automatically build related objects on this chain and return the final object. Note that the name of the returned object is still XX xx. XX. By reading the source code, we will find that the getInstance() method of this factory class is thread safe.
  • Then we introduce the shutdown method of category object. The shutdown method of hierachymaintainer class is still called inside this class. The following logic is executed inside this method. First, call the removeAllAppenders method of all created category objects, Then calling many internal callback functions (when an exception occurs, then the callback function will not be executed later). As for the feature of using callback function to perform additional operations, it is not used in the whole category design. Also, in the destructor of HierachyMaintainer, the shutdown method is called first, then the removeAllCategories method is called. The former has already introduced the latter, and the internal implementation is delete all the Category objects that have been instantiated to avoid leakage of memory.
  • For HierachyMaintainer, there is only one object of this class actually used by category. Category obtains HierachyMaintainer object through HierachyMaintainer::getDefaultMaintainer method. This is a HierachyMaintainer class, which does not privatize the constructor, so we think HierachyMaintainer may be a singleton design in a non strict sense, because it provides a singleton factory method.
  • Next, we choose a specific log operation to introduce. We choose the debug operation. We explain the detection method of Category class for some levels, isPriorityEnabled method, which is internal detection_ Whether priority is greater than the priority passed in by the function. Then we introduced several overloading methods of debug, and debugStream method, debug two overloading methods execution logic is consistent, when there are formatted strings and variable parameters, they will construct variable parameters and call them. logUnconditionally method, which internally converts formatted strings and variable parameters into strings using StringUtil::vform. Then call logUnconditionally2 to do the real log operation, this method will use some information to build the LoggingEvent object, then call the notification method callAppenders introduced by us, LoggingEvent object notifies all registered Apender objects that have been registered, and according to the The additivity property to determine whether this event is passed to the parent Category object
  • Finally, we explained the implementation of the debugStream method. The internal call is the getStream method, and the getStream method returns a CategoryStream object, and its second parameter is set to Priority::NOTSET according to whether the corresponding priority operation is supported.
  • For CategoryStream, an std::ostringstream object is actually aggregated internally. It takes this object as a buffer and provides a template method operator < < that supports common types to support most types of output streams. For this template method, we also explained in detail that it will support the internal before output_ The priority attribute is used to judge. If it is only Priority::NOTSET, no operation will be performed. This is why the Category::getStream method is implemented like that. CategoryStream also provides us with functions to control the flow output, such as eol, such as left. In short, we can try this method when we don't want to use formatted string + variable parameters.

Topics: C++ Cpp