log4cpp源码阅读:Appender组件学习
发布日期:2022-03-16 03:25:44 浏览次数:33 分类:技术文章

本文共 29272 字,大约阅读时间需要 97 分钟。

功能

Appender是作为Category类的观察者而存在的,它投个接收方法doAppender,是用来执行输出操作的,更确切的说,它表示要输出的地址,置于输出的格式则交给Layout组件。

在这里插入图片描述
我们可以看到,Appender类是所有Appender相关类的基类。

ps:

  • 观察者模式:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
  • 一般情况下,被依赖的对象叫做被观察者,依赖的对象叫做观察者(Observer)

Appender类

Appender类是作为所有类的基类存在,同时它也维护了所有构建好的对象信息。这里的维护,并不是负责这些对象内存的回收工作,它只是作为一个map,这个map可以输出appender对象的名称来获得对应的appender对象。

class Appender {
protected: Appender(const std::string& name);public: virtual ~Appender();private: typedef std::map
AppenderMap; const std::string _name; //表示当前Appender对象的名称public: class AppenderMapStorage {
public: Appender::AppenderMap* _allAppenders; //appender name -> Appender*的映射 threading::Mutex _appenderMapMutex; // 用来辅助实现线程安全的 AppenderMapStorage(); ~AppenderMapStorage(); }; static AppenderMapStorage &_appenderMapStorageInstance; }

可以看到,Appender的构造函数访问权限是保护的,并且析构函数是公有的虚函数,说明这个类是作为基类实现的,而且不能被单独实例化。

我们来看一下它的构造函数和析构函数实现

class Appender {
public: static void _addAppender(Appender* appender); // 两个静态辅助函数 static void _removeAppender(Appender* appender); static Appender* getAppender(const std::string& name);} // 构造函数Appender::Appender(const std::string& name) : _name(name) {
_addAppender(this);//其实内部就是添加当前对象到_allAppenders中} Appender::~Appender() {
_removeAppender(this);//将当前对象从_allAppenders中移除}void Appender::_addAppender(Appender* appender) {
threading::ScopedLock lock(_appenderMapStorageInstance._appenderMapMutex); //线程安全的 _getAllAppenders()[appender->getName()] = appender; }void Appender::_removeAppender(Appender* appender) {
threading::ScopedLock lock(_appenderMapStorageInstance._appenderMapMutex); //线程安全的 _getAllAppenders().erase(appender->getName());}Appender::AppenderMap& Appender::_getAllAppenders() {
return *_appenderMapStorageInstance._allAppenders; //_appenderMapStorageInstance是一个静态成员函数,只会有一个。相当于单例模式}

我们可以发现,Appender是通过在构造函数中,每当构造函数被调用的时候(也说明肯定有Appender子类被实例化了),就将this添加到_allAppenders中,析构的时候,会将this从 _allAppenders中移除(但是没有delete this),也就是说释放内存不归Appender管,他保存的只是类的引用(这儿是类比说法,实际上 是指针)。

明白了这里的原理,我们回到Category::getAppender方法

Appender* Category::getAppender(const std::string& name) const {
threading::ScopedLock lock(_appenderSetMutex); {
AppenderSet::const_iterator i = _appender.begin(); if (_appender.end() != i) {
// found return((*i)->getAppender(name)); } else {
return(NULL); } } }

它内部调用的就是一个Appender::getAppender方法,这个方法内部返回的 就是_allAppender[name]的值:

Appender* Appender::getAppender(const std::string& name) {
threading::ScopedLock lock(_appenderMapStorageInstance._appenderMapMutex); AppenderMap& allAppenders = Appender::_getAllAppenders(); AppenderMap::iterator i = allAppenders.find(name); return (allAppenders.end() == i) ? NULL : ((*i).second); }

到目前为止,我们为了说明Appender类保存所有已经创建对象引用,已经连带介绍了Appender类的几个静态方法了,下面吧剩下的几个静态方法 也一次性地介绍完。

class Appender {
public: static bool reopenAll(); // 重新打开所有已经实例化号的Appender对象 static void closeAll();private: static void _deleteAllAppenders(); //这个方法应该是个废弃方法,他是delete _allAppenders内部所有元素,但是因为这个方法是 //私有的,我也没有发现这个方法有被其他方法调用的痕迹。(所以这个方法就不介绍了)}// 重新打开所有的appender对象// 当有一个对象打开失败,返回值都为false,否则truebool Appender::reopenAll() {
threading::ScopedLock lock(_appenderMapMutex); //线程安全的 bool result = true; AppenderMap& allAppenders = _getAllAppenders(); // 遍历容器中的每一个元素,调用他们的reopen方法 for(AppenderMap::iterator i = allAppenders.begin(); i != allAppenders.end(); i++) {
result = result && ((*i).second)->reopen(); } return result;}// 关闭所有的appender对象(或者说:调用所有appender对象的close方法)void Appender::closeAll() {
threading::ScopedLock lock(_appenderMapMutex); //线程安全的 AppenderMap& allAppenders = _getAllAppenders(); // 逻辑和close方法那儿完全一样,这是这儿调用的每一个appender对象的close方法,并且有返回值 for(AppenderMap::iterator i = allAppenders.begin(); i != allAppenders.end(); i++) {
((*i).second)->close(); }}

即:

  • Appender::reopen静态方法可以重新打开所有的已经被实例化好的Appender子类对象
  • Appender::close静态方法可以重新关闭所有已经被实例化好的Appender子类对象

上面介绍的其实都是Appender类提供的静态工具方法,下面我们来看一下它的真正职责的实现,以及它的内部方法设计

class Appender {
public: // 功能:将event输出到指定的目标中(同时也是观察者模式中的接收方法) virtual void doAppend(const LoggingEvent& event) = 0; // 重新打开某个关联的目标,比如tcp、file...因为重新打开可能会失败,所以这个方法设计的返回值来说明重新打开重新与否 virtual bool reopen() = 0; //关闭某个关联的输出目标 virtual void close() = 0; // 这里的设计其实是用来辅助配置文件的设计的 // 用来标识这个Appender对象内部使用的Layout策略 virtual bool requiresLayout() const = 0; // 当requiresLayout()为true时,那么这个方法就有用了,可以用来设置内部的Layout策略 virtual void setLayout(Layout* layout) = 0; // 用来设置Appender可以支持什么等级的日志事件 // 其实category哪里已经有了一道关卡,就是Category::setPriority,它也相当于一个阈值 //第二道关卡就是Appender的setThreshold方法设置的阈值 //第三道关卡是下面介绍的Filter机制。(其实第三道关卡在Appender的子类实现中并没有被使用) //所以认为这个Filter类介绍与否都没有多大关系,但是它是设计模式中责任链模式设计的范本,所以在下面章节,还会聊聊这个类 virtual void setThreshold(Priority::Value priority) = 0; // 这儿是获取通过上面方法设置的权限阀值 virtual Priority::Value getThreshold() = 0; // 用来设置Filter对当前Appender virtual void setFilter(Filter* filter) = 0; // 获取已经设置好的Filter对象 virtual Filter* getFilter() = 0; // 获取当前appender的名称 inline const std::string &getName() const {
return _name;}}

对照着上面注释代码看,可以看到Appender内部定义了5种类型的纯虚方法

  • 观察者模式中的通知方法:doAppend
  • 和真正输出目标进行打交道的方法:reopen、close
  • 和Layout策略类相关的方法:requiresLayout、setLayout
  • 进行设置输出的阈值机制:get/setThreshold
  • 进行限制输出的Filter机制

Filter类

class Filter {
public: Filter(); virtual ~Filter();private: Filter* _chainedFilter;}Filter::Filter() : _chainedFilter(NULL) {
}Filter::~Filter() {
if (_chainedFilter) delete _chainedFilter;}

你会发现,他肯定是一个链式结构,但是这个链式如何构成的呢,我们会发现,这个类的构造器并没有提供一个参数来让我们设置 下一个Filter,然后通过阅读阅读源码我们会发现,他有一个setChainedFilter方法,这只是一个set接口,除了这个方法以外,他 还有一个appendChainedFilter方法。

class Filter {
public: ... virtual void setChainedFilter(Filter* filter); virtual void appendChainedFilter(Filter* filter); virtual Filter* getEndOfChain(); virtual Filter* Filter::getChainedFilter(); ...private: Filter *_chainedFilter;};// 主要是用来设置_chainedFilter属性,首先会删除掉原来的属性然后才会设置新的属性void Filter::setChainedFilter(Filter* filter) {
if (filter != _chainedFilter) {
if (_chainedFilter) delete _chainedFilter; _chainedFilter = filter; }}// 获取当前Filter链的尾部Filter* Filter::getEndOfChain() {
Filter* end = this; while(end->getChainedFilter()) {
end = end->getChainedFilter(); } return end;}// 将filter设置为当前链的最尾部的_chainedFiltervoid Filter::appendChainedFilter(Filter* filter) {
Filter* end = getEndOfChain(); end->setChainedFilter(filter);}// 只是简单地获取_chainedFilter属性Filter* Filter::getChainedFilter() {
return _chainedFilter;}

其实到目前为止,上面的介绍的方法都只是用来改变Filter类的内部结构。它的真正的负责其主要业务的方法是decide()方法:

public Filter {
public: typedef enum {
DENY=-1, NEUTRAL=0, ACCEPT=1} Decision; ... // 进行决策,具体看下面源码实现介绍 virtual Decision decide(const LoggingEvent& event);protected: virtual Decision _decide(const LoggingEvent& event) = 0; ...};// 进行真正的决策操作,返回ACCEPT表示允许输出,返回DENY表示禁止输出// 返回NEUTRAL表示持中立态度Filter::Decision Filter::decide(const LoggingEvent& event) {
// 每个Filter的具体进行决策操作是_decide方法 Filter::Decision decision = _decide(event); // 当当前Filter迟中立态度的时候,它才会把决策权继续交给下一个_chainedFilter if ((Filter::NEUTRAL == decision) && getChainedFilter()) {
decision = getChainedFilter()->decide(event); } return decision;}

看到Filter的decide方法,可以当当前Filter对象对于event持中立态度的时候,会将决策权,传给链上的_chainedFilter对象进行决策, 这个链上的Filter对象执行相似的流程,。。。因此这个Filter类就是一个责任链设计,这个类可以通过appendFilter来添加 尾部链,相对于使用构造器来设置下一条链上的内容,这样使用更加灵活,更加方便。所以说他可以作为责任链模式的一种范本 来看。

我们再回到这个decide方法中,Filter对象真正决策的方法是一个纯虚方法_decide方法,真正的Filter实际应该实例化这个方法来做出具体的决策。

正如上面所说,这个Filter类的机制是如此优雅,但是在具体的Appender子类中,我没有看到这个Filter被被真正使用(没有可以直接 被实例化的子类)。

下面我们会按照刚开始的那张类图,来逐渐分析,log4cpp中可以直接用来使用的Appender子类。

AppenderSkeleton

正如其名称一样,这个列是所有Appender子类的骨架类,这个类内部实现了关于Appender接口中对于输出操作包含拦截的机制,即Filter机制和threshold机制。

class AppenderSkeleton : public Appender {
private: Priority::Value _threshold; Filter* _filter;public: virtual void setThreshold(Priority::Value priority); virtual Priority::Value getThreshold(); virtual void setFilter(Filter* filter); virtual Filter* getFilter();}
void AppenderSkeleton::setThreshold(Priority::Value priority) {
_threshold = priority;} Priority::Value AppenderSkeleton::getThreshold() {
return _threshold;} void AppenderSkeleton::setFilter(Filter* filter) {
if (_filter != filter) {
if (_filter) delete _filter; _filter = filter; }} Filter* AppenderSkeleton::getFilter() {
return _filter;}

我们可以看到,Filter内部实现了get/setFilter方法和get/setThreshold方法。下面我们来看一下doAppend的实现。

class AppenderSkeleton : public Appender {
public: ... virtual void doAppend(const LoggingEvent& event);protected: virtual void _append(const LoggingEvent& event) = 0; //纯虚方法};//实现了Appender::doAppend方法void AppenderSkeleton::doAppend(const LoggingEvent& event) {
// 首先检测权限阀值,逻辑和Category内部isPriorityEnabled逻辑是完全一样的 // 只要event.priority比设置过来的_threshold小,那么那么能够通过阀值关卡的 if ((Priority::NOTSET == _threshold) || (event.priority <= _threshold)) {
// 然后检测filter,这儿一个需要注意的是,他会检测_filter是否为空,为空的话说明不存在Filter链,直接进行真正的添加操作 // 或者_filter不为空,那么他会调用此对象的decide方法来进行决策,只要返回的不是Filter::DENY,那么也会进行真正的添加操作 // 真正添加操作的执行是在_append方法中 if (!_filter || (_filter->decide(event) != Filter::DENY)) {
_append(event); } }}

我们可以看到,doAppend方法内部会经过两道关卡检测,当检测通过,则调用一个纯虚方法_append方法,很明显这个方法是由子类实现的。 那么后面的子类其实只要直接继承AppenderSkeleton类,然后实现_append方法,那么客户类调用Appender的doAppend方法的时候,其实 内部就会首先经过两道关卡检测,当检测通过,他会调用真正实现类的_append方法(其实就是一个模板设计模式的实现)

上面也说明,后面的具体的AppenderSkeleton的子类其实他们的真正核心逻辑都是在_doAppend方法中,我们可以选择这个方法为突入口, 不用盲目的看。

除了上面介绍的核心方法以外,他的内部还实现了reopen方法,内部只是直接返回true,这儿只是说一下,记住reopen方法AppenderSkeleton 已经实现了。

bool AppenderSkeleton::reopen() {
return true;}

LayoutAppender

class LayoutAppender : public AppenderSkeleton {
public: virtual bool requiresLayout() const; virtual void setLayout(Layout* layout = NULL);protected: Layout& _getLayout();private: Layout* _layout; //用来保存设置进来的Layout对象}// 内部返回true,表示继承AppenderSkeleton的类都支持设置一个Layout策略bool LayoutAppender::requiresLayout() const {
return true;}// 具体的layout方法的设置void LayoutAppender::setLayout(Layout* layout) {
if (layout != _layout) {
Layout *oldLayout = _layout; _layout = (layout == NULL) ? new DefaultLayoutType() : layout; delete oldLayout; }}// 获取_layout属性Layout& LayoutAppender::_getLayout() {
return *_layout; }

这个类关键就是实现了一个setLayout方法,就是设置内部的_layout属性的值,其内部有对新的值进行检测的逻辑,如果新的值为NULL,那么就会创建DefaultLayoutType来设置给_layout,我们来看一下他到底是什么类型。

class LayoutAppender {
public: typedef BasicLayout DefaultLayoutType; //他就是BasicLayout类型 ...};

DefaultLayoutType,就是前面我们在Layout一章中介绍的BasicType类型。

下面我们来看一下LayoutAppender 的构造函数

class LayoutAppender : public AppenderSkeleton {
public: LayoutAppender(const std::string& name); virtual ~LayoutAppender(); }// 我们可以看到,_layout也会有一个默认的值,就是DefaultLayoutType对象LayoutAppender::LayoutAppender(const std::string& name) : AppenderSkeleton(name), _layout(new DefaultLayoutType()) {
} LayoutAppender::~LayoutAppender() {
delete _layout;}

我们通过这个构造方法可以知道所有的Layout的子对象,其实内部都是有一个默认的Layout的对象的(DefaultLayoutType类型)。

到目前为止,我们已经介绍了所有真正能够被直接使用的Appender实现类的基类部分了,下面我们来看一看我们平时使用log4cpp经常接触到 的Appender类型。

OstreamAppender

OstreamAppender类表示输出目标地址是std::ostream对象。

我们先来看下它的数据成员和构造方法

class OstreamAppender : public LayoutAppender {
public: OstreamAppender(const std::string& name, std::ostream* stream); virtual ~OstreamAppender();protected: std::ostream* _stream; // 保存最后要真正输出的目的地}// 构造方法也很简单,就是调用基类构造器,然后设置自己内部的成员 OstreamAppender::OstreamAppender(const std::string& name, std::ostream* stream) : LayoutAppender(name), _stream(stream) {
}

可以看出OstreamAppender只有一个_stream成员,用来保存真正要进行输出操作的输出流。

另外,在AppenderSkeleton类中,它模板话了doAppend方法,使得这个类的子类不用直接实现doAppend方法,只需要实现_append方法就可以进行真正的输出操作了。

// 请注意类的继承层次关系// OstreamAppender继承LayoutAppender,LayoutAppender继承AppenderSkeleton// 所以说OstreamAppender也是AppenderSkeleton的子类,所以下面他能够实现AppenderSkeleton的_append方法class OstreamAppender : public LayoutAppender {
protected: ... virtual void _append(const LoggingEvent& event); //实现了AppenderSkeleton中的_append方法 ...};void OstreamAppender::_append(const LoggingEvent& event) {
// _getLayout()获取的是AppenderLayout的_layout属性,即使没有调用setLayout方法来设置指定的Layout对象 // 内部也使用一个默认的Layout的,即BasicLayout类型(默认设置时机发生在LayoutAppender的构造函数中) // // Layout接口有一个format方法,作用就是将LoggingEvent格式化为一个字符串 (*_stream) << _getLayout().format(event); //将Layout::format的接口输出到_stream中 if (!_stream->good()) {
// XXX help! help! }}

我们总结一下_append方法的实现逻辑:

  • 调用_getLayout().format(event)。其实内部真正调用的是LayoutAppender的_layout.format(event)返回格式化后 的结果。
  • 将格式化后的内容输出到_stream中。 非常简洁功能确是不简单,我们可以通过setLayout来设置不同的Layout对象,然会利用Layout对象的格式化功能可以将LoggingEvent格式化为多种格式的内容。 这个就是使用策略设计模式的好处所在。

这个类还实现了close和open方法,我们来简单看一下。

class OstreamAppender {
public: ... virtual bool reopen(); virtual void close(); ...};void OstreamAppender::close() {
// empty}bool OstreamAppender::reopen() {
return true;}

如Appender中所说,reopen和close方法直接操作的是_stream对象,不过这儿我们发现close方法居然什么也没有做,为什么呢?还记得我们经常使用new OstreamAppender(“name”, &std::cout)来将日志内容输出到控制台中,std::cout是不能由我们手动关闭的,它是整个程序控制的,因为close方法内部并没有真正关闭,所以reopen方法也就没有重新打开的意义了,所以内部直接返回true。

所以回过头来想,在介绍OstreamAppender方法之前,说这个Appender可以支持各种输出流为目的地址是正确的,但是因为他内部并没有维护 _stream对象(我们在析构方法中会看到,他并没有delete _stream)。并且没有实现reopen和close,所以如果用来他支持文件输出流的话,要 手动维护对象的生命周期,手动销毁。

OstreamAppender::~OstreamAppender() {
close(); // 只是调用close方法,但是close方法什么也没有做,没有delete _stream}

总结:OstreamAppender其实完全适合输出流对象,其实只有各种标准输出流。适合比如文件这样的目标的是FileAppender

FileAppender

FileAppender 表示输出目标是一个普通的文件,并且它允许我们设置一个标准来决定是追加内容到已有文件上,还是清空原来文件输入新的内容。

我们来看一下它的构造函数和数据成员

class FileAppender : public LayoutAppender {
public: FileAppender(const std::string& name, const std::string& fileName, bool append = true, mode_t mode = 00644); ...protected: // 下面的属性,你可以通告观看构造函数的实现来明白他们的含义 const std::string _fileName; // 文件打开名称 int _fd; // 代表一个文件(就相当于一个文件句柄) int _flags; // 文件打开方式 mode_t _mode; // 文件共享模式,他有一个默认值00644,此值经过实测共享读操作}FileAppender::FileAppender(const std::string& name, const std::string& fileName, bool append, mode_t mode) : LayoutAppender(name), _fileName(fileName), _flags(O_CREAT | O_APPEND | O_WRONLY), _mode(mode) {
if (!append) _flags |= O_TRUNC; // O_TRUNC标志位:打开的文件当输入新的内容的时候,会将原来的内容清空。 _fd = ::open(_fileName.c_str(), _flags, _mode);}

再来看一下对于FileAppender内部成员进行设置和获取的相关方法。

class FileAppender : public LayoutAppender {
public: virtual void setAppend(bool append); virtual bool getAppend() const; virtual void setMode(mode_t mode); virtual mode_t getMode() const;}void FileAppender::setAppend(bool append) {
if (append) {
_flags &= ~O_TRUNC; } else {
_flags |= O_TRUNC; }}bool FileAppender::getAppend() const {
return (_flags & O_TRUNC) == 0;}void FileAppender::setMode(mode_t mode) {
_mode = mode;}mode_t FileAppender::getMode() const {
return _mode;}

接下来我们来看一下FileAppender的输出方法_append方法

class FileAppender : public LayoutAppender {
public: ... void _append(const LoggingEvent& event); ...private: ...};// 内部操作就是调用c函数直接将Layout对象处理后的内容输出到指定文件中void FileAppender::_append(const LoggingEvent& event) {
std::string message(_getLayout().format(event)); if (!::write(_fd, message.data(), message.length())) {
// XXX help! help! }}

因为FileAppender的目标是文件,所以_append方法内部其实就是将event被Layout对象格式化的结果直接输出到指定文件中。

其实到目前位置FileAppender的几个核心内容,已经讲解完毕,我们来看一下他剩余部分的代码。

class FileAppender : public LayoutAppender {
public: ... FileAppender(const std::string& name, int fd); virtual ~FileAppender(); virtual bool reopen(); virtual void close(); ...private: ...};// 看多第一个构造函数,看他意思也很清楚了// fd是外部传过来的一个已经执行过open函数返回的文件描述符FileAppender::FileAppender(const std::string& name, int fd) : LayoutAppender(name), _fileName(""), _fd(fd), _flags(O_CREAT | O_APPEND | O_WRONLY), _mode(00644) {
}// 析构时候,执行closeFileAppender::~FileAppender() {
close();}// close方法内部执行::close函数关闭之前打开的文件void FileAppender::close() {
if (_fd!=-1) {
::close(_fd); _fd=-1; }}// 重新打开_fileName文件bool FileAppender::reopen() {
if (_fileName != "") {
int fd = ::open(_fileName.c_str(), _flags, _mode); //fd 保存新打开的文件描述符 if (fd < 0) return false; else {
if (_fd != -1) //关闭老的文件 ::close(_fd); _fd = fd; //设置新的文件 return true; } } else {
return true; // 当原来的文件名为空,这儿也返回true // 说明这个返回值为false,当且仅当::open函数内部发生错误 } }

我们会发现open, reopen其实都是对内部的_fd直接进行操作的。需要说一下FileAppender::reopen方法,其内部首先检测文件 名称是否为空,为空则直接返回true,否则打开新的文件,打开失败返回false,然后关闭老的文件,设置新的文件,返回true。

RollingFileAppender

这个类的功能是非常强大的,在构造这个类的对象的时候,我们可以指定一个最大文件尺寸,和最大备份索引,当输出文件尺寸大于我们设置的最大值的时候,它会将原来的文件做一个备份,然后清空原来内容继续输出。当文件的数目大于等于最大备份索引的时候,则会删除最老的备份,然后将新的内容做一份备份

我们首先看一个这个类的构造函数和数据成员

class RollingFileAppender : public FileAppender {
public: RollingFileAppender(const std::string& name, const std::string& fileName, size_t maxFileSize = 10*1024*1024, unsigned int maxBackupIndex = 1, bool append = true, mode_t mode = 00644);protected: unsigned int _maxBackupIndex; // 最大备份索引 unsigned short int _maxBackupIndexWidth; // 其实他表示的是保存一个_maxBackupIndex需要多个个字符// 内部是以log10(_maxBackupIndex) + 1来设置的值 size_t _maxFileSize; // 最大文件尺寸}// 构造函数RollingFileAppender::RollingFileAppender(const std::string& name, const std::string& fileName, size_t maxFileSize, unsigned int maxBackupIndex, bool append, mode_t mode) : FileAppender(name, fileName, append, mode), _maxBackupIndex(maxBackupIndex > 0 ? maxBackupIndex : 1), //_maxBackupIndex肯定是大于等于1的 _maxBackupIndexWidth((_maxBackupIndex > 0) ? log10((float)_maxBackupIndex)+1 : 1), //设置_maxBackupIndexWidth = log10(_maxBackupIndex) + 1 _maxFileSize(maxFileSize) {
}

我们可以看到RollingFileAppender是FileAppender的子类,他的内部相比于FileAppender多了两个属性,一个_maxFileSize,一个 _maxBackupIndex。

再来看一下RollingFileAppender的 _append方法的实现:

class RollingAppender : public FileAppender {
public: ... virtual void rollOver();protected: // 实现AppenderSkeleton的_append方法 virtual void _append(const LoggingEvent& event);};void RollingFileAppender::_append(const LoggingEvent& event) {
FileAppender::_append(event); // 首先输出文件内容 off_t offset = ::lseek(_fd, 0, SEEK_END); // 获取文件尺寸 if (offset < 0) {
// XXX we got an error, ignore for now } else {
if(static_cast
(offset) >= _maxFileSize) {
// 文件内容大于设置的最大文件尺寸时候,进行回滚 rollOver(); } }}

先输出文件内容,然后获取文件尺寸,最终比较文件尺寸是否大于从构造器中设置的最大文件尺寸来决定 是否进行文件回滚操作

void RollingFileAppender::rollOver() {
::close(_fd); //关掉原来的文件 // 大于0说明需要进行文件备份,内部就是备份文件的实现部分 if (_maxBackupIndex > 0) {
std::ostringstream filename_stream; // 在这句代码,你会发现_maxBackupIndexWidth的真正作用,假如_maxBackupIndex为9,那么他只需要1个字符可可以包含这个索引了 // 假如_maxBackupIndex为11,那么他就需要2个字符才可以包含这个最大索引 // 这儿你会恍然大悟,为什么在构造器中,_maxBackupIndexWidth = log10(_maxBackupIndex) + 1,现在我们知道高中数学学好对于编程也是非常有用的 filename_stream << _fileName << "." << std::setw( _maxBackupIndexWidth ) << std::setfill( '0' ) << _maxBackupIndex << std::ends; // remove the very last (oldest) file std::string last_log_filename = filename_stream.str(); std::cout << last_log_filename << std::endl; ::remove(last_log_filename.c_str()); //移除掉最后一个文件,文件名称是_fileName + ftoa(_maxBackupIndex) //其实你可以假想一下,刚开始的是,是没有一个备份文件的,但是假如我设置_maxBackupIndex=10 //他能够删除掉这个文件吗,答案显然是不能的,但是这儿有没有捕获任何结果,他是故意的 //即使发生错误,也不关心的 // 因为最后一个文件都已经被删除了 // 那么这儿他会将前一个索引的文件都重命名为后一个索引的文件 // 执行下面所有循环的结果是第一个索引的备份文件名称,已经无用了,也是说他可以被别的文件当做文件名 for(unsigned int i = _maxBackupIndex; i > 1; i--) {
filename_stream.str(std::string()); filename_stream << _fileName << '.' << std::setw( _maxBackupIndexWidth ) << std::setfill( '0' ) << i - 1 << std::ends; // set padding so the files are listed in order ::rename(filename_stream.str().c_str(), last_log_filename.c_str()); last_log_filename = filename_stream.str(); } // 将_fileName命名为第一个索引的文件 ::rename(_fileName.c_str(), last_log_filename.c_str()); } _fd = ::open(_fileName.c_str(), _flags, _mode); //重新打开新的文件(内部其实会创建新的文件)}

可以看出内部根本没有发生拷贝文件的操作,它们内部实现是通过重命名来实现的,执行流程如下:

  • 删除最后一个备份文件(名称以ftoa(_maxBackupIndex)为索引),我们需要知道这个文件不一定存在,但是也会被执行删除操作。
  • 将前一个备份文件之前的文件名称,都依次重命名为后一个备份文件的名称,在这儿也会发生后一个备份文件根本就不存在的情况,但是这些 都没有关系,因为只要有备份文件,他们就会被重命名为后一个备份文件。
  • 重命名当前文件名称为所以为1的备份文件名称。

DailyRollingFileAppender

DailyRollingFileAppender和RollingFileAppender相比,后者是根据最大文件尺寸来决定是否对当前文件进行备份,而DailyRollingFileAppender是根据设置的最大保存天数来对当前文件进行备份。

先看一下其成员和构造函数

class DailyRollingFileAppender : public FileAppender {
public: DailyRollingFileAppender(const std::string& name, const std::string& fileName, unsigned int maxDaysToKeep = maxDaysToKeepDefault, bool append = true, mode_t mode = 00644);protected: unsigned int _maxDaysToKeep; //最大保存天数 struct tm _logsTime; //用来记录指定文件最后被修改的时}//构造函数实现DailyRollingFileAppender::DailyRollingFileAppender(const std::string& name, const std::string& fileName, unsigned int maxDaysToKeep, bool append, mode_t mode) : FileAppender(name, fileName, append, mode), _maxDaysToKeep(maxDaysToKeep != 0 ? maxDaysToKeep : maxDaysToKeepDefault) {
struct stat statBuf; int res; time_t t; res = ::stat(fileName.c_str(), &statBuf); //获取_fnameName名称的文件的状态 if (res < 0) {
//发生某些错误会进入下面内部代码块,因为FileAppender当fileName文件不存在的时候,会创建文件,一般不会执行下面代码 t = time(NULL); // 可能指定的fileName路径不存在时候(就是路径上的目录不存在)可能文件无法创建 } else {
t = statBuf.st_mtime; } // 就是将utc时间转换为本地时间 localtime_s(&_logsTime, &t);// 执行过上面方法后,我们可以知道_logsTime保存的是_fileName名称的文件的最后修改时间}

我们可以发现DailyRollingFileAppender内部有两个成员:

  • _maxDaysToKeep 用来记录最大保存天数
  • _logsTime 当前文件名称最后被修改的时间

下面我们来看一下_append方法的实现源码

void DailyRollingFileAppender::_append(const log4cpp::LoggingEvent &event){
struct tm now; time_t t = time(NULL); //获取是当前时间(utc时间) bool timeok = localtime_s(&now, &t) == 0; // 将utc时间转换为本地时间 // 当前时间和当前输出文件的最后修改时间如果不是在同一天的话,那么就说明可能要进行文件备份了 // 或者要删除某些过期文件了 if (timeok) {
if ((now.tm_mday != _logsTime.tm_mday) || (now.tm_mon != _logsTime.tm_mon) || (now.tm_year != _logsTime.tm_year)) {
rollOver(); //执行真正的日志备份,或者删除操作 _logsTime = now; } } // 输出日志事件到指定文件中 log4cpp::FileAppender::_append(event);}

内部的实现步骤在这儿在语言重复一下:

  • 获取当前时间
  • 使用当前时间和当前输出文件的最后修改时间进行比较如果两个时间不是在同一天的话,则 进行备份操作,注意备份行为是rollOver方法实现的
  • 输出当前event到输出文件中

下面我们来看一下rollOver方法的实现

void DailyRollingFileAppender::rollOver(){
std::ostringstream filename_s; // 关闭当前文件 int res_close = ::close(_fd); if (res_close != 0) {
std::cerr << "Error closing file " << _fileName << std::endl; } //对当前文件做一份备份,备份后缀一个是当前文件的最后修改时间 filename_s << _fileName << "." << _logsTime.tm_year + 1900 << "-" << std::setfill('0') << std::setw(2) << _logsTime.tm_mon + 1 << "-" << std::setw(2) << _logsTime.tm_mday << std::ends; const std::string lastFn = filename_s.str(); int res_rename = ::rename(_fileName.c_str(), lastFn.c_str()); if (res_rename != 0) {
std::cerr << "Error renaming file " << _fileName << " to " << lastFn << std::endl; } //重新创建新的文件 _fd = ::open(_fileName.c_str(), _flags, _mode); if (_fd == -1) {
std::cerr << "Error opening file " << _fileName << std::endl; } // 当前时间 - 最大备份时间 得到 的是文件最迟创建时间,一旦某个备份文件时间早于这个文件时间,说明这个文件超期了 // 那么就要进行删除 const time_t oldest = time(NULL) - _maxDaysToKeep * 60 * 60 * 24; //_maxDaysToKeep * 60 * 60 * 24是的将最大保存天数,转换为最大保存秒数#define PATHDELIMITER "/" const std::string::size_type last_delimiter = _fileName.rfind(PATHDELIMITER); const std::string dirname((last_delimiter == std::string::npos)? "." : _fileName.substr(0, last_delimiter)); //获取的目录路径 const std::string filname((last_delimiter == std::string::npos)? _fileName : _fileName.substr(last_delimiter+1, _fileName.size()-last_delimiter-1)); // 获取的是文件名称 struct dirent **entries; int nentries = scandir(dirname.c_str(), &entries, 0, alphasort); //获取这个目录下的所有文件 if (nentries < 0) return; for (int i = 0; i < nentries; i++) {
//遍历 // 获取查找到的文件时间 struct stat statBuf; const std::string fullfilename = dirname + PATHDELIMITER + entries[i]->d_name; int res = ::stat(fullfilename.c_str(), &statBuf); if ((res == -1) || (!S_ISREG(statBuf.st_mode))) {
free(entries[i]); continue; } // 如果查找的文件不是目录并且创建时间比oldest还要早的话,那么说明这个文件超期,使用::unlink函数删除 if (statBuf.st_mtime < oldest && strstr(entries[i]->d_name, filname.c_str())) {
std::cout << " Deleting " << fullfilename.c_str() << std::endl; ::unlink(fullfilename.c_str()); } free(entries[i]); } free(entries);}

我们可以看到上面的代码逻辑也是非常清楚的,主要执行如下流程:

  • 将当前文件重命名为以此文件最后被修改的时间为后缀
  • 创建新的文件,就是执行reopen方法
  • 计算出最早备份时间
  • 利用响应平台的api来查找_fileName开头的文件名称,并且查找到的文件不是目录并且修改时间比上面计算出的 最早备份时间还要早的话,那么就将这个文件删除掉。

StringQueueAppender

内部直接输出目标是一个std::queue对象

下面看一下其成员和构造函数

class StringQueueAppender : public LayoutAppender{
public: StringQueueAppender(const std::string& name); virtual ~StringQueueAppender(); protected: std::queue
_queue;};StringQueueAppender::StringQueueAppender(const std::string& name) : LayoutAppender(name) {
}StringQueueAppender::~StringQueueAppender() {
close(); //内部实现为空}

我们可以看到,其内部聚合了一个std::queue<std::string>类型的变量

下面我们来看一下它的_append方法。

void StringQueueAppender::_append(const LoggingEvent& event) {
_queue.push(_getLayout().format(event));}

也是非常简单,就是将最终的输出内容入队。

reopen/close方法

bool StringQueueAppender::reopen() {
return true;} void StringQueueAppender::close() {
// empty}

因为std::queue根本不是什么系统资源,所以reopen和close内部实现为空。

剩余的一些方法就是访问_queue属性的一些相关方法

public:    // 返回_queue.size()    virtual size_t queueSize() const;     // return _queue    virtual std::queue
& getQueue(); // return _queue virtual const std::queue
& getQueue() const; // 返回队首元素,或者为空 virtual std::string popMessage();};std::queue
& StringQueueAppender::getQueue() {
return _queue;}const std::queue
& StringQueueAppender::getQueue() const {
return _queue;}size_t StringQueueAppender::queueSize() const {
return getQueue().size();}std::string StringQueueAppender::popMessage() {
std::string message; // 当_queue内部不为空的时候,才会返回队首元素 if (!_queue.empty()) {
message = _queue.front(); _queue.pop(); } return message;}

总结

  • 首先介绍了Appender类,分析了他的相关方法,以及其拦截机制Filter类的源码(责任链模式)
  • 然后是AppenderSkeleton类,他是所有Appender实现类的骨架类,内部实现了两个阀值机制
    • 一种使用get/setThreshold来设置阀值,属性 保存内部成员_threshold中,还提供一个Filter机制
    • 在doAppend方法中,首先检测event的priority是否超过了指定的阀值(threshold), 当在阀值内,会检测Filter链是否存在,如果存在,会调用_filter.decide方法来让Filter进行决策看是否支持此event输出,如果也支持才会 进行真正的输出
    • 输出方法被AppenderSkeleton设置为一个纯虚方法_append方法,所有的子类只要实现这个方法就可以完成真正输出操作。
  • 然后介绍了LayoutAppender类,它继承了AppenderSkeleton:
    • 实现了requiresLayout方法,内部直接返回true;
    • 然后实现了setLayout方法,将设置的layout对象保存 在_layout属性中。注意有一个默认的Layout对象是BasicLayout
  • OstreamAppender,他继承LayoutAppender。它是一种使用标准输出流作为输出环境的Appender,内部聚合了一个std::ostream类型的对象,在_append中, 直接调用_stream << _getLayout().format(event),来讲结果进行输出。
  • FileAppender,这个类继承LayoutAppender:
    • 这个方法构造函数参数是appenderName, fileName, append, mode
    • 内部操作 文件函数是::open函数
    • 在_append方法中使用::write方法来讲结果输出
    • 在close方法中,调用::close方法来关闭文件
  • RollingFileAppender和DailyRollingFileAppender,都是继承FileAppender前者可以设置最大尺寸和 最大备份数目来进行文件备份,后者可以设置最大保存天数来实现文件备份
  • StringQueueAppender,最终目标是std::queue<std::string>类型,内部有一个此种类型对象_queue成员 。内部的_append方法实现是_queue.push(getLayout().format(event))。

转载地址:https://blog.csdn.net/zhizhengguan/article/details/123227711 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:一些基础的面试总结
下一篇:软件工程:白天开会,加班写代码怎么破

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月20日 10时33分07秒