一.
使用開源項目最大的好處就是可以看它的源碼來加深你的理解,理解了其實現原理,則使用起來必定更加得心應手。
下面幾個類是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中的幾個重要的概念和類的實現都講了,還有其他的一些細節值得關注,例如:跨平臺的實現,線程安全,配置文件的解析等等,有興趣的話,可以去翻翻源碼,對理解這個開源庫還是很有幫助的。