之前写了muduo的Logger类的分析,今天来看一下LogFile类及其相关的类分析,之前的Logger是控制日志怎样写,怎样用,怎样实现,而今天的LogFile则是用来控制日志怎样和文件打交道。几天写一次?多少行写一次?我们下面来看一下它的相关实现。
1.muduo的日志滚动
(1)文件大小,例如每天写满1G换下一个文件
(2)时间(每天零点新建一个日志文件,不论前一个文件是否写满)
2.典型日志文件名
logfile_test.20130411-115604.popo.7743.log
构造参数:1.const string& basename,即最后/符号后的字符串
2.bool threadSafe = true
3.int flushInterval = 3
下面一看就一目了然了:
成员变量:
private:
void append_unlocked(const char* logline, int len); //不加锁的append方式
static string getLogFileName(const string& basename, time_t* now); //获取日志文件的名称
const string basename_; //日志文件basename
const size_t rollSize_; //日志文件达到rolsize生成一个新文件
const int flushInterval_; //日志写入间隔时间
const int checkEveryN_;
int count_; //计数器,检测是否需要换新文件
boost::scoped_ptr mutex_; //加锁
time_t startOfPeriod_;//开始记录日志时间(调整至零点时间,如12.04:11.24和 11.12.04:12.50,调整零点都是12.04:00.00,是同一天,只用来比较同一天,和日志名无关
time_t lastRoll_; //上一次滚动日志文件时间
time_t lastFlush_; //上一次日志写入文件时间
boost::scoped_ptr file_; //文件智能指针
const static int kRollPerSeconds_ = 60*60*24; //即时间一天
构造函数:
LogFile::LogFile(const string& basename,
size_t rollSize,
bool threadSafe,
int flushInterval,
int checkEveryN)
: basename_(basename),
rollSize_(rollSize),
flushInterval_(flushInterval),
checkEveryN_(checkEveryN),
count_(0),
mutex_(threadSafe ? new MutexLock : NULL), //不是线程安全就不需要构造mutex_
startOfPeriod_(0),
lastRoll_(0),
lastFlush_(0)
{
assert(basename.find('/') == string::npos); //断言basename不包含'/'
rollFile();
}
>base_name_,日志文件basename
>startOfPeriod_,time_t类型,开始记录日志时间 (调整至零点时间),UTC时间
>lastRoll_,time_t类型,上一次滚动日志文件时间
>lastFlush_,time_t类型,上一次文件写入日志文件时间
>muduo库独特之处在于使用了一个threadsafe标志,来决定是否分配mutex_,如果为假,将不使用mutex_,非线程安全但是提高了效率。
其他成员变量不用介绍了,上面已经给过注释了。
主要我们来看一下它的内部类:
boost::scoped_ptr file_; //文件智能指针
private:
size_t write(const char* logline, size_t len);
FILE* fp_; //文件指针
char buffer_[64*1024]; //缓冲区,64K
size_t writtenBytes_; //已经写入的字节数
构造函数:
FileUtil::AppendFile::AppendFile(StringArg filename)
: fp_(::fopen(filename.c_str(), "ae")), // 'e' for O_CLOEXEC
writtenBytes_(0) //已经写入的字节数
{
assert(fp_);
::setbuffer(fp_, buffer_, sizeof buffer_); //诗经文件指针fp_的缓冲区设定64K,也就是文件的stream大小
// posix_fadvise POSIX_FADV_DONTNEED ?
}
>LogFile使用了成员AppendFile类,这个类在以前的muduo版本里面名字是File。AppendFile类构造时会以fopen的“ae”方式打开文件,也就是append和closeonexec。
write函数:
size_t FileUtil::AppendFile::write(const char* logline, size_t len)
{
// #undef fwrite_unlocked
return ::fwrite_unlocked(logline, 1, len, fp_); //不加锁的方式写入,效率高,not thread safe
}
注意:AppendFile类内部的write函数调用的是::fwrite_unlocked()函数,以不加锁的方式写文件,不线程安全,但是效率高。
append函数:
void FileUtil::AppendFile::append(const char* logline, const size_t len)
{
size_t n = write(logline, len); //n是已经写了的字节数
size_t remain = len - n; //相减大于0表示未写完
while (remain > 0) //剩余写入字节数大于0,继续写,直到写完
{
size_t x = write(logline + n, remain); //同样x是已经写的
if (x == 0)
{
int err = ferror(fp_);
if (err)
{
fprintf(stderr, "AppendFile::append() failed %s\n", strerror_tl(err));
}
break;
}
n += x; //偏移
remain = len - n; // remain -= x
}
writtenBytes_ += len; //已经写入个数
}
下面是源码分析:
LogFile.h
#ifndef MUDUO_BASE_LOGFILE_H
#define MUDUO_BASE_LOGFILE_H
#include
#include
#include
#include
namespace muduo
{
namespace FileUtil
{
class AppendFile;
}
class LogFile : boost::noncopyable
{
public:
LogFile(const string& basename,
size_t rollSize,
bool threadSafe = true,
int flushInterval = 3,
int checkEveryN = 1024); //默认分割行数1024
~LogFile();
void append(const char* logline, int len); //将一行长度为len添加到日志文件中
void flush(); //刷新
bool rollFile();
private:
void append_unlocked(const char* logline, int len); //不加锁的append方式
static string getLogFileName(const string& basename, time_t* now); //获取日志文件的名称
const string basename_; //日志文件basename
const size_t rollSize_; //日志文件达到rolsize生成一个新文件
const int flushInterval_; //日志写入间隔时间
const int checkEveryN_;
int count_; //计数器,检测是否需要换新文件
boost::scoped_ptr mutex_; //加锁
time_t startOfPeriod_; //开始记录日志时间(调整至零点时间?)
time_t lastRoll_; //上一次滚动日志文件时间
time_t lastFlush_; //上一次日志写入文件时间
boost::scoped_ptr file_; //智能指针文件
const static int kRollPerSeconds_ = 60*60*24; //即时间一天
};
}
#endif // MUDUO_BASE_LOGFILE_H
LogFile.cpp
#include
#include
#include
#include
#include
#include
using namespace muduo;
LogFile::LogFile(const string& basename,
size_t rollSize,
bool threadSafe,
int flushInterval,
int checkEveryN)
: basename_(basename),
rollSize_(rollSize),
flushInterval_(flushInterval),
checkEveryN_(checkEveryN),
count_(0),
mutex_(threadSafe ? new MutexLock : NULL),
startOfPeriod_(0),
lastRoll_(0),
lastFlush_(0)
{
assert(basename.find('/') == string::npos);
rollFile();
}
LogFile::~LogFile()
{
}
void LogFile::append(const char* logline, int len) //追加日志文本
{
if (mutex_) //如果new过锁了,说明需要线程安全,那么调用加锁方式
{
MutexLockGuard lock(*mutex_);
append_unlocked(logline, len);
}
else
{
append_unlocked(logline, len); //否则调用非加锁方式
}
}
void LogFile::flush()
{
if (mutex_)
{
MutexLockGuard lock(*mutex_);
file_->flush();
}
else
{
file_->flush();
}
}
void LogFile::append_unlocked(const char* logline, int len)
{
file_->append(logline, len); //调用成员变量file_的方法,也就是FileUtil.h中的AppendFile的不加锁apeend方法
if (file_->writtenBytes() > rollSize_) //写入字节数大小超过滚动设定大小
{
rollFile(); //滚动
}
else
{
++count_; //增加行数
if (count_ >= checkEveryN_)
{
count_ = 0;
time_t now = ::time(NULL);
time_t thisPeriod_ = now / kRollPerSeconds_ * kRollPerSeconds_;
if (thisPeriod_ != startOfPeriod_) //比较事件是否相等,不等就是第二天0点,那么滚动
{
rollFile();
}
else if (now - lastFlush_ > flushInterval_) //判断是否超过flush间隔时间,超过了就flush,否则什么都不做。
{
lastFlush_ = now; //更新
file_->flush();
}
}
}
}
bool LogFile::rollFile()
{
time_t now = 0;
string filename = getLogFileName(basename_, &now); //获取生成一个文件名称
//注意,这里先除KRollPerSeconds然后乘KPollPerSeconds表示对齐值KRollPerSeconds的整数倍,也就是事件调整到当天零点(/除法会引发取整)
time_t start = now / kRollPerSeconds_ * kRollPerSeconds_;
if (now > lastRoll_) //如果大于lastRoll,产生一个新的日志文件,并更新lastRoll
{
lastRoll_ = now;
lastFlush_ = now;
startOfPeriod_ = start;
file_.reset(new FileUtil::AppendFile(filename));
return true;
}
return false;
}
string LogFile::getLogFileName(const string& basename, time_t* now)
{
string filename;
filename.reserve(basename.size() + 64); //预分配内存
filename = basename;
char timebuf[32];
struct tm tm;
*now = time(NULL);
gmtime_r(now, &tm); // FIXME: localtime_r ? //获取当前时间,UTC时间,_r是线程安全,
strftime(timebuf, sizeof timebuf, ".%Y%m%d-%H%M%S.", &tm); //格式化时间,strftime函数
filename += timebuf;
filename += ProcessInfo::hostname(); //根据processinfo模块内部调用::gethostname函数获取主机名
char pidbuf[32];
snprintf(pidbuf, sizeof pidbuf, ".%d", ProcessInfo::pid()); //进程号
filename += pidbuf;
filename += ".log";
return filename;
}
FileUtili.h
#ifndef MUDUO_BASE_FILEUTIL_H
#define MUDUO_BASE_FILEUTIL_H
#include
#include
namespace muduo
{
namespace FileUtil
{
// read small file < 64KB
class ReadSmallFile : boost::noncopyable //小文件读取
{
public:
ReadSmallFile(StringArg filename);
~ReadSmallFile();
// return errno
template
int readToString(int maxSize,
String* content, //读入content缓冲区
int64_t* fileSize,
int64_t* modifyTime,
int64_t* createTime);
/// Read at maxium kBufferSize into buf_
// return errno
int readToBuffer(int* size);
const char* buffer() const { return buf_; }
static const int kBufferSize = 64*1024;
private:
int fd_;
int err_;
char buf_[kBufferSize];
};
// read the file content, returns errno if error happens.
template
int readFile(StringArg filename,
int maxSize,
String* content,
int64_t* fileSize = NULL,
int64_t* modifyTime = NULL,
int64_t* createTime = NULL)
{
ReadSmallFile file(filename);
return file.readToString(maxSize, content, fileSize, modifyTime, createTime); //调用read_string
}
// not thread safe
class AppendFile : boost::noncopyable
{
public:
explicit AppendFile(StringArg filename);
~AppendFile();
void append(const char* logline, const size_t len);
void flush();
size_t writtenBytes() const { return writtenBytes_; }
private:
size_t write(const char* logline, size_t len);
FILE* fp_;
char buffer_[64*1024];
size_t writtenBytes_;
};
}
}
#endif // MUDUO_BASE_FILEUTIL_H
.h文件中提供了一个全局函数,readFile函数,调用ReadSmallFile类中的readToString方法,供外部将小文件中的内容转化为字符串。
#include
#include // strerror_tl
#include
#include
#include
#include
#include
#include
using namespace muduo;
//不是线程安全的
FileUtil::AppendFile::AppendFile(StringArg filename)
: fp_(::fopen(filename.c_str(), "ae")), // 'e' for O_CLOEXEC
writtenBytes_(0) //已经写入的字节数
{
assert(fp_);
::setbuffer(fp_, buffer_, sizeof buffer_); //文件指针的缓冲区设定64k
// posix_fadvise POSIX_FADV_DONTNEED ?
}
FileUtil::AppendFile::~AppendFile()
{
::fclose(fp_); //关闭文件指针
}
//不是线程安全的,需要外部加锁
void FileUtil::AppendFile::append(const char* logline, const size_t len)
{
size_t n = write(logline, len);
size_t remain = len - n;
while (remain > 0) //剩余写入字节数大于0
{
size_t x = write(logline + n, remain);
if (x == 0)
{
int err = ferror(fp_);
if (err)
{
fprintf(stderr, "AppendFile::append() failed %s\n", strerror_tl(err));
}
break;
}
n += x;
remain = len - n; // remain -= x
}
writtenBytes_ += len;
}
void FileUtil::AppendFile::flush()
{
::fflush(fp_);
}
size_t FileUtil::AppendFile::write(const char* logline, size_t len)
{
// #undef fwrite_unlocked
return ::fwrite_unlocked(logline, 1, len, fp_); //不加锁的方式写入,效率高,not thread safe
}
FileUtil::ReadSmallFile::ReadSmallFile(StringArg filename)
: fd_(::open(filename.c_str(), O_RDONLY | O_CLOEXEC)),
err_(0)
{
buf_[0] = '\0';
if (fd_ < 0)
{
err_ = errno;
}
}
FileUtil::ReadSmallFile::~ReadSmallFile()
{
if (fd_ >= 0)
{
::close(fd_); // FIXME: check EINTR
}
}
/////////////////////////////////////////////////////////////////////////////////////////
//下面是提供的两个模板函,是RredSmallFile类中的分别是读取到字符串string类型和读取到缓冲区buffer类型
// return errno
template
int FileUtil::ReadSmallFile::readToString(int maxSize,
String* content,
int64_t* fileSize,
int64_t* modifyTime,
int64_t* createTime)
{
BOOST_STATIC_ASSERT(sizeof(off_t) == 8);
assert(content != NULL);
int err = err_;
if (fd_ >= 0)
{
content->clear();
if (fileSize) //如果不为空,获取文件大小
{
struct stat statbuf; //fstat函数用来 获取文件(普通文件,目录,管道,socket,字符,块()的属性
if (::fstat(fd_, &statbuf) == 0) //fstat用来获取文件大小,保存到缓冲区当中
{
if (S_ISREG(statbuf.st_mode))
{
*fileSize = statbuf.st_size; //stat结构体中有st_size参数就是文件大小,串给输入参数指针
content->reserve(static_cast(std::min(implicit_cast(maxSize), *fileSize)));
}
else if (S_ISDIR(statbuf.st_mode))
{
err = EISDIR;
}
if (modifyTime) //修改时间,创建时间等
{
*modifyTime = statbuf.st_mtime;
}
if (createTime)
{
*createTime = statbuf.st_ctime;
}
}
else
{
err = errno;
}
}
while (content->size() < implicit_cast(maxSize))
{
//读操作
size_t toRead = std::min(implicit_cast(maxSize) - content->size(), sizeof(buf_));
ssize_t n = ::read(fd_, buf_, toRead);//从文件当中读取数据到字符串
if (n > 0)
{
content->append(buf_, n); //追加到字符串
}
else
{
if (n < 0)
{
err = errno;
}
break;
}
}
}
return err;
}
//读取到缓冲区
int FileUtil::ReadSmallFile::readToBuffer(int* size)
{
int err = err_;
if (fd_ >= 0)
{
ssize_t n = ::pread(fd_, buf_, sizeof(buf_)-1, 0); //pread和read区别,pread读取完文件offset不会更改,
if (n >= 0) //以前在哪还在哪,而read会引发offset随读到额字节数移动
{
if (size)
{
*size = static_cast(n);
}
buf_[n] = '\0';
}
else
{
err = errno;
}
}
return err;
}
//对成员函数进行模板的显示实例化,提高效率
template int FileUtil::readFile(StringArg filename,
int maxSize,
string* content,
int64_t*, int64_t*, int64_t*);
template int FileUtil::ReadSmallFile::readToString(
int maxSize,
string* content,
int64_t*, int64_t*, int64_t*);
#ifndef MUDUO_STD_STRING
template int FileUtil::readFile(StringArg filename,
int maxSize,
std::string* content,
int64_t*, int64_t*, int64_t*);
template int FileUtil::ReadSmallFile::readToString(
int maxSize,
std::string* content,
int64_t*, int64_t*, int64_t*);
#endif
RreadSmallFile类提供了readToString,readToBuffer,分别是转化为字符串(string类型),和转化到缓冲区(实际上也可以认为是字符串char*类型)。
注意,模板的显示实例化:
template
void f(T) throw(T)
{}
//four types
template void f(int) throw(int);
template void f<>(float) throw(float);
template void f(long) throw(long);
template void f(char);
上面四种显示实例化方法都是有效的,采用模板显示实例化可以减小编译器实例化展开的开销。