muduo网络库源码复现笔记(十六):base库的LogFile.h

Muduo网络库简介

muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕。它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程网络应用程序。
muduo网络库的核心代码只有数千行,在网络编程技术学习的进阶阶段,muduo是一个非常值得学习的开源库。目前我也是刚刚开始学习这个网络库的源码,希望将这个学习过程记录下来。这个网络库的源码已经发布在GitHub上,可以点击这里阅读。目前Github上这份源码已经被作者用c++11重写,我学习的版本是没有使用c++11版本的。不过二者大同小异,核心思想是没有变化的。点这里可以看我的源代码,如果你对我之前的博客有兴趣,可以点击下面的连接:
muduo网络库源码复现笔记(一):base库的Timestamp.h
muduo网络库源码复现笔记(二):base库的Atomic.h
muduo网络库源码复现笔记(三):base库的Exception.h
muduo网络库源码复现笔记(四):base库的Thread.h和CurrentThread.h
muduo网络库源码复现笔记(五):base库的Mutex.h和Condition.h和CoutntDownLatch.h
muduo网络库源码复现笔记(六):base库的BlockingQueue.h和BoundedBlockingQueue.h
muduo网络库源码复现笔记(七):base库的ThreadPool.h
muduo网络库源码复现笔记(八):base库的Singleton.h
muduo网络库源码复现笔记(九):base库的ThreadLocalSingleton.h
muduo网络库源码复现笔记(十):base库的ThreadLocalSingleton.h
muduo网络库源码复现笔记(十一):base库的StringPiece.h
muduo网络库源码复现笔记(十二):base库的LogStream.h
muduo网络库源码复现笔记(十三):base库的Logging.h
muduo网络库源码复现笔记(十四):base库的FileUtil.h
muduo网络库源码复现笔记(十五):base库的ProcessInfo.h

1 LogFile.h

LogFile.h头文件中的LogFile类实现的是滚动日志的功能。所谓滚动日志,它具有两个功能:(1)当日志大小达到一定数目时如1G,自动开启一个新日志;(2)当到达每天零点时,开启一个新日志。同时我们对日志的命名也有明确的规范,是按运行程序名称.年-月-日-时-分-秒-微秒.主机名称.log的格式来命名的。下面我们来看看具体的实现过程。

1.1 File类

LogFile类能实现滚动日志要借助于File类。Logfile类有个成员boost::scoped_ptr file_(scoped_ptr就是c++11里的unique_ptr智能指针),当我们要写入新日志时只需要new出一个File类初始化它。下面分析一些File的关键函数。

class LogFile::File : boost::noncopyable
{
public:
	explicit File(const string& filename)
		:	fp_(::fopen(filename.data(),"ae")),
			writtenBytes_(0)
	{
		assert(fp_);
		::setbuffer(fp_,buffer_,sizeof buffer_);	
	}

	~File()
	{
		::fclose(fp_);
	}

	void append(const char* logline,const size_t len)
	{
		size_t n = write(logline,len);
		size_t remain = len - n;
		while(remain > 0)
		{
			size_t x = write(logline+n,remain);
			if(x == 0)
			{
				int err = ferror(fp_);
				if(err)
				{
					fprintf(stderr,"LogFile::FIle::append() failed %s\n",
							strerror_tl(err));
				}
				break;
			}
			n += x;
			remain = len - n;
		}
		writtenBytes_ += len;	
	}

	void flush()
	{
		::fflush(fp_);
	}
	
	size_t writtenBytes() const {return writtenBytes_;}
private:
	size_t write(const char* logline,size_t len)
	{
#undef fwrite_unlocked
		return ::fwrite_unlocked(logline,1,len,fp_);
	}

	FILE* fp_;
	char buffer_[64*1024];
	size_t writtenBytes_;
};

1.1 File类的构造与析构函数

在构造函数中,使用fopen打开文件,用返回的文件指针初始化fp_;writtenBytes_是这个文件已经写入的字节数,初始化为0;在构造函数的逻辑中,将文件缓冲区设置为buffer_,buffer_大小是64k。
析构函数中关闭文件指针即可。

1.2File类的append函数

append函数用于向文件写入细节。若写入字节后remain > 0,接着在while中写入。注意append中使用的write函数不是fcntl.h的write函数,而是File类中封装的write函数。这里的write函数封装了fwrite_locked,不使用锁也不判断其他函数是否使用锁,所以不是线程安全的,当然如果有线程安全考虑的话也可以在外部调用它时加锁。

2 LogFile类

接下来进入正题,看LogFile类如何实现滚动日志。LogFile的私有成员变量中
basename_是指运行程序的basename,rollSize_是指字节数达到多少时开启一个新日志,flushInterval_是flush间隔。startPeriod_是指一个日志开启的当天0点的时刻(距离UTC 1970-1-1-0:00的秒数),lastRoll是上次开启新日志的时刻,lastFlush是上次flush的时刻。这些变量在构造函数中初始化。还有两个常量kCheckTimeRoll_与count_配合检查是否达到第二天,后面在append_unlocked函数中讲。kRollPerSeconds_就是一天的秒数。

lass LogFile : boost::noncopyable
{
public:
	LogFile(const string& basename,size_t rollSize,
				bool threadSafe = true,int flushInterval = 3);
	~LogFile();
	
	void append(const char* logline,int len);
	void flush();

private:
	void append_unlocked(const char* logline,int len);
	
	static string getLogFileName(const string& basename,time_t* now);
	void rollFile();

	const string basename_;//basename of logfile;
	const size_t rollSize_; //maxsize of a roll logfile
	const int flushInterval_; //

	int count_;

	boost::scoped_ptr mutex_;
	time_t startOfPeriod_; 
	time_t lastRoll_;
	time_t lastFlush_;
	class File;
	boost::scoped_ptr file_;

	const static int kCheckTimeRoll_ = 1024;
	const static int kRollPerSeconds_ = 60 * 60 * 24;
};

2.1 LogFile构造函数

LogFile函数的构造函数如下,计数count_被初试化为0, startOfPeriod_、lastRoll_、lastFlush_同样都是0。初始化完毕,使用rollFile()函数开启一个新日志。

LogFile::LogFile(const string& basename,size_t rollSize,
					bool threadSafe,int flushInterval)
	:	basename_(basename),rollSize_(rollSize),
		flushInterval_(flushInterval),
		count_(0),
		mutex_(threadSafe ? new MutexLock : NULL),
		startOfPeriod_(0),
		lastRoll_(0),
		lastFlush_(0)
{
	assert(basename.find('/') == string::npos);
	rollFile();	
}

2.2 rollFile函数–开启新日志

在rollFile中,首先使用getLogFileName函数(代码忽略,不难,我的github有)获取日志的规范名称(前面提到过),并且讲now置为现在的时刻。注意start的赋值,它将now进行了取整,得到的结果就是now当天的零点时刻。随后进行赋值与file_指针初始化。rollFile函数除了在构造函数中使用,还在append函数中使用以实现滚动功能

void LogFile::rollFile()
{
	time_t now = 0;
	string filename = getLogFileName(basename_,&now);
	time_t start = now / kRollPerSeconds_ * kRollPerSeconds_;
	
	if(now > lastRoll_)
	{
		lastRoll_ = now;
		lastFlush_ = now;
		startOfPeriod_ = start;
		file_.reset(new File(filename));
	}
}

2.3 append函数–公开字节写入函数

append函数是公开以被调用向日志写入字节。append函数有两个参数,一个是要写入的内容,一个是内容长度。根据是否线程安全的考虑来决定是否加锁,而真正进行字节写入的函数是私有的append_unlocked;

void LogFile::append(const char* logline,int len)
{
	if(mutex_)
	{
		MutexLockGuard lock(*mutex_);
		append_unlocked(logline,len);
	}
	else
	{
		append_unlocked(logline,len);
	}
}

2.4 append_unlocked–实现日志滚动

append_unlocked是一个私有函数,它的函数参数和公有的append一样,这个函数实现的前面提到的日志滚动的效果。如果我们要向日志中写入字节,直接调用file_的append即可。写完之后检查目前日志的字节数是否大于rollSize_,若是,开启新日志,这样实现了滚动日志的第一个功能要求。接下来检查计数器count_是否大于kChekTimeRoll_。只有count_ > kCheckTimeRoll_时,才会检查目前是否到了第二天。检查是否到第二天的方法依旧是将当前时刻取整与startPeriod比较,这样就实现了第二个功能。

void LogFile::append_unlocked(const char* logline,int len)
{
	file_ -> append(logline,len);
	
	if(file_->writtenBytes() > rollSize_)
	{
		rollFile();
	}
	else
	{
		if(count_ > kCheckTimeRoll_)
		{
			count_ = 0;
			time_t now = ::time(NULL);
			time_t thisPeriod_ = now / kRollPerSeconds_ * kRollPerSeconds_;
			if(thisPeriod_ != startOfPeriod_)
			{
				rollFile();
			}
			else if(now - lastFlush_ > flushInterval_)
			{
				lastFlush_ = now;
				file_ -> flush();
			}
		}
		else
		{
			++count_;
		}	
	}
}

2.5 使用

我们可以使用Logging定义的宏来实现日志输入。如

boost::scoped_ptr g_logfile;

void outputFunc(const char* msg,int len)
{
	g_logfile -> append(msg,len);
} 
g_logfile.reset(new muduo::LogFile(::basename(filename),150 * 1024));//日志容量上限150k
	muduo::Logger::setOutput(outputFunc);//设定Logger的输出逻辑。

在主函数中可以使用LOG*来向日志输入字节信息了。

LOG_INFO<<"messages";

你可能感兴趣的:(muduo)