Easylogging++源碼分析

        一.

    使用開源項目最大的好處就是可以看它的源碼來加深你的理解,理解了其實現原理,則使用起來必定更加得心應手。

       下面幾個類是Easylogging中最重要的幾個類,弄明白了這幾個類就能弄懂各項功能的實現:

        Loger:調試者

        RegisteredLoggers:調試者倉庫,即多個調試者的集合

        Writer:調試器

        Configuration:配置器

        Configurations:配置器倉庫,即多個配置器的集合


         調試者,調試器,配置器三者的關係如下:

         

          當開始一次log輸出時,Writer找到一個對應的Loger,該Loger保存了自己的配置信息,根據這些配置信息,Writer決定是否輸出該log,或者以什麼格式將log輸出到哪個文件中。

           上面的流程就是每次log的輸出過程。從源碼中可以很清晰的看到這個流程,展開宏LINFO:

#define LINFO CINFO("trivial")
            "trivial"是Loger的標識符號,表明這條log將使用"trivial"調試器。類似的還有其他的調試器:
#define BINFO CINFO("business")
#define SINFO CINFO("security")
            等等調試器。再繼續一層層的展開宏,最後可以發現:
#define _ELPP_LOG_WRITER(_logger, _level) easyloggingpp::internal::Writer(\
    _logger, easyloggingpp::internal::Aspect::Normal, _level, __func__, __FILE__, __LINE__)
              實際上是定義了一個Writer匿名對象。所以,每當我們開始一條log的輸出時,就會生成一個Writer匿名對象。對於匿名對象,在其定義的地方,一行結束後即是其生命週期的結束,即這時會執行其析構函數。

              Writer的構造函數中,會根據loggerId_(即前面說的"trivial")獲取到對應的loger:

logger_ = registeredLoggers->get(loggerId_, false);
            這裏的registeredLoggers就是Loger倉庫了,這個倉庫是什麼時候在哪裏建立的?這個會在後面講。              

               然後在Writer的析構函數中,會進行log的真正處理,調用buildAndWriterLine()函數,這個函數做的事如下:

               (1)先從loger中拿到配置信息:

TypedConfigurations* conf_ = logger_->typedConfigurations_;
            (2)根據配置信息,加載log到Writer的成員變量currLine_中,展示一部分代碼如下:
 if (f_ & constants_->kAppName) {
            v_ = logger_->applicationName();
            fs_ = constants_->APP_NAME_FORMAT_SPECIFIER;
            internal::utilities::LogManipulator::updateFormatValue(fs_, v_, currLine_, constants_);
        }
        // Logger ID
        if (f_ & constants_->kLoggerId) {
            v_ = logger_->id();
            fs_ = constants_->LOGGER_ID_FORMAT_SPECIFIER;
            internal::utilities::LogManipulator::updateFormatValue(fs_, v_, currLine_, constants_);
        }
  // Log message
        if (f_ & constants_->kLogMessage) {
            fs_ = constants_->LOG_MESSAGE_FORMAT_SPECIFIER;
            internal::utilities::LogManipulator::updateFormatValue(fs_, logger_->stream()->str(), currLine_, constants_);
               其中,上面的Log message即是我們本身要輸出的,其保存在logger_的std::stringstream* stream_;中。例如:
LINFO<<"test!";
               即這時我們要輸出的test!"信息就保存中stream_成員中。這個過程就是在之前的文章<Easylogging的封裝使用>中講到的,在Writer 的<<操作符重載函數中,其針對每種數據類型都有對應的重載函數,包括一些第三方庫的數據類型,或者自定義的類型,例如下面幾個例子:
  inline Writer& operator<<(const std::string& log_) {
        if (!proceed_) { return *this; }
        _ELPP_STREAM(logger_) << log_;
        return *this;
    }
    inline Writer& operator<<(signed short log_) {
        if (!proceed_) { return *this; }
        _ELPP_STREAM(logger_) << log_;
        return *this;
    }
    inline Writer& operator<<(const QStringRef& log_) {
        if (!proceed_) { return *this; }
        return operator<<(log_.toString());
    }
    template <class Class>
    inline Writer& operator<<(const Class& class_) {
        if (!proceed_) { return *this; }
        _ELPP_STREAM(logger_) << class_;
        return *this;
    }
              (3)根據配置信息,將currLine_輸出到文件或者標誌輸出中:
 if (logger_->stream_) {
            if (logger_->typedConfigurations_->toFile(severity_)) {
                safeWriteToFile(severity_, logger_, currLine_);
            }
            if (logger_->typedConfigurations_->toStandardOutput(severity_)) {
                std::cout << currLine_;
            }
             到這裏,一條log就輸出完畢。

二.

      前面講到Writer會根據logerId從Loger倉庫中拿到一個對應的Loger,那這個Loger倉庫從哪來的呢?在<Easylogginggpp介紹和簡單使用>:http://blog.csdn.net/woshichenweixian/article/details/77018452 這篇文章中講到,要使用Easylogging,必須先調用初始化宏一次:

_INITIALIZE_EASYLOGGINGPP

           展開該宏:

#define _INITIALIZE_EASYLOGGINGPP                                 \
    namespace easyloggingpp {                                     \
        namespace internal {                                      \
            ScopedPointer<RegisteredLoggers> registeredLoggers(   \
                    new RegisteredLoggers());                     \
        }                                                         \
    }
           該宏其實就是在easylogginggpp::internal作用域內定義了一個ScopedPointer<RegisteredLoggers>對象。ScopedPointer是Easylogging內部實現的一個智能指針模板類,類似於C++11的  shared_ptr。這裏創建的智能指針要管理的對象是RegisteredLoggers,所以在這裏給智能指針傳的參數是new 一個RegisteredLoggers對象,至此,Loggers倉庫創建完成。另外需要說的一點是,這裏用智能指針進行倉庫管理的好處是,我們只需在剛開始使用Easylogging時進行初始化,而不用在不使用Easylogging時去手動清理。

           RegisteredLoggers的構造函數中會創建幾個默認的Logger:

registerNew(new Logger("trivial", constants_, conf));
        registerNew(new Logger("business", constants_));
        registerNew(new Logger("security", constants_));
registerNew(new Logger("performance", constants_, confPerformance));
            之前提到的"trivial" Loger就是在這時創建的。

            再來看看RegisterdLoggers是如何管理多個Loger的,這個的實現也很有意思。RegisteredLoggers繼承於一個模板類

template<class Class, class Predicate>
class Registry {
            作者是這樣解釋Registry模板類的使用的:
//! Internal repository base to manage memory on heap. Used internally, you should not use it.
            即它是一個基於堆內存管理的內部倉庫。模板參數Class是管理的對象的類型,模板參數Predicate是一個函數對象,用來判斷Logger是否符合某個條件

            Registry類的數據成員如下:

private:
    std::vector<Class*> list_;
            即通過std::vector管理了所有對象的指針,每當註冊一個新的對象時,就是把該對象的指針推到vector中:

inline void registerNew(Class* c_) {
        list_.push_back(c_);
    }
           從倉庫中獲取一個特定的Loger時,會遍歷整個vector,通過模板參數Predicate這個函數對象去判斷是否是要找的那個Logger:
   template<typename T, typename T2>
    Class* get(const T& t_, const T2& t2_) {
        Iterator iter = std::find_if(list_.begin(), list_.end(), Predicate(t_, t2_));
        if (iter != list_.end() && *iter != NULL) {
            return *iter;
        }
        return NULL;
    }
           RegisteredLoggers就是通過這個Registry類來管理多個Logger的:
class RegisteredLoggers : public internal::Registry<Logger, Logger::Predicate>
            實際上,Configurations和Configuration之間的關係類似於RegisteredLoggers和Logger的關係,都是倉庫管理多個對象,所以Configurations的實現也是繼承於Registry的:

class Configurations : public internal::Registry<internal::Configuration, internal::Configuration::Predicate>

三.

       Easylogging中的幾個重要的概念和類的實現都講了,還有其他的一些細節值得關注,例如:跨平臺的實現,線程安全,配置文件的解析等等,有興趣的話,可以去翻翻源碼,對理解這個開源庫還是很有幫助的。

           



               





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章