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("curity")
等等调试器。再继续⼀层层的展开宏,最后可以发现:
#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_, fal);
这⾥的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_, cu凉拌花生米
rrLine_, 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 <clas小学五年级古诗
s 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(verity_)) {
safeWriteToFile(verity_, logger_, currLine_);
}
if (logger_->typedConfigurations_->toStandardOutput(verity_)) {
std::cout << currLine_;
}
到这⾥,⼀条log就输出完毕。
⼆.
_INITIALIZE_EASYLOGGINGPP
展开该宏:
#define _INITIALIZE_EASYLOGGINGPP \
namespace easyloggingpp { \
namespace internal { \
ScopedPointer<RegisteredLoggers> registeredLoggers( \
new RegisteredLoggers()); &nbs秋天的味道
p; \
} \
}
该宏其实就是在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("curity", constants_));
registerNew(new Logger("performance", constants_, confPe养胃的菜
rformance));
之前提到的"trivial" Loger就是在这时创建的。
再来看看RegisterdLoggers是如何管理多个Loger的,这个的实现也很有意思。RegisteredLoggers继承于⼀个模板类
template<class Class, class Predicate>
class Registry {
作者是这样解释Registry模板类的使⽤的:
//! Internal repository ba to manage memory on heap. Ud internally, you should not u 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::Registr门缝里看人歇后语
y<Logger, Logger::Predicate>
实际上,Configurations和Configuration之间的关系类似于RegisteredLoggers和Logger的关系,都是仓库管理多个对象,所以Configurations的实现也是继承于Registry的:
class Configurations : public internal::Registry<internal::Configuration, internal::Configuration::Predicate>
三.
Easylogging中的⼏个重要的概念和类的实现都讲了,还有其他的⼀些细节值得关注,例如:跨平台的实现,线程安全,配置⽂件的解析等等,有兴趣的话,可以去翻翻源码,对理解这个开源库还是很有帮助的。