假如让你自己去写一个日志程序,我想最原始且简单的想法因该是,首先将要写入日志文件的内容转化为字符串,然后调用write系统调用将其写入文件。这种实现方法的确就是我们程序最原始的日志方法。这种做法无疑是十分低效的,那么如何让我们的日志能够高效起来呢?本篇博文就是要给大家分享一种高效的日志–异步日志
1.异步日志要实现什么?
前言中我有告诉打下那种最原始且简单的日志方法很低效,那么它低效在什么地方了呢?
主要有如下几点
1.其每条日志内容都会调用write,我们都知道write为系统调用,每一条就调一次它,势必系统开销会很大
2.当我们在关键的地方调用write会不会导致关键部分的代码不能即使的执行?
上述俩个导致我们原始日志低效的主要原因的解决方案分别为:
(1)既然每条日志调用一次write会导致系统开销变大,那么我们就设计一个buffer将日志内容保存在buffer中,待buffer满之后在一次性调用write将其写入即可
(2)由于write可能会阻塞在当前位置,导致紧随其后的关键代码可能不能马上执行,那么我们就单独开个线程专门来执行write调用不就可以了么
根据我们的解决方案,我们可以总结出异步日志的基本流程因该为:
前段线程(我们的程序)负责将日志写入buffer中,当buffer写满之后,将buffer转交给后端线程(我们的负责专门调用write来写日志的线程),也就是一个典型的多生产者单消费者模式
2.异步日志的代码实现
接下来为大家介绍一下我用C++实现的的异步日志代码
(1)buffer类的实现
#ifndef FIX_BUFFER_H_
#define FIX_BUFFER_H_
#include <vector>
#include <string>
#include <assert.h>
#include <fcntl.h>
namespace netlib
{
class FixBuffer
{
public:
FixBuffer()
:buffer_(1024*1024*4) //初始化大小为4M
{
readableIndex_ = 0;
writeableIndex_ = 0;
}
~FixBuffer()
{
}
int readableSize(void) //可读字节数
{
return writeableIndex_ - readableIndex_;
}
int writeableSize(void) //可写字节数
{
return buffer_.size() - writeableIndex_;
}
void append(const char *data,int len) //添加数据到buffer中
{
std::copy(data,data + len,getWritePeek());
moveWriteIndex(len);
}
char *getReadPeek(void) //获得读位置的指针
{
return begin() + readableIndex_;
}
char *getWritePeek(void) //获得写位置的指针
{
return begin() + writeableIndex_;
}
void moveWriteIndex(int len) //移动可写下标
{
writeableIndex_ = writeableIndex_ + len;
}
void resetBuffer(void) //重置buffer
{
readableIndex_ = 0;
writeableIndex_ = 0;
}
private:
char *begin()
{
return &*buffer_.begin();
}
std::vector<char> buffer_;
int readableIndex_;
int writeableIndex_;
};
}
#endif
buffer用std::vector来实现,主要提供的对外接口为获取buffer的读写位置指针,可读可写的大小,往buffer中添加数据等,buffer为固定大小4M
(2)用于将日志写如文件的类
logfile.h
#ifndef LOG_FILE_H_
#define LOG_FILE_H_
namespace netlib
{
class LogFile
{
public:
LogFile(int rollSize);
~LogFile();
//往磁盘里添加消息
void append(char *log,int len);
//滚动文件
void rollFile(