muduo库的LogFile日志文件类剖析


之前写了muduo的Logger类的分析,今天来看一下LogFile类及其相关的类分析,之前的Logger是控制日志怎样写,怎样用,怎样实现,而今天的LogFile则是用来控制日志怎样和文件打交道。几天写一次?多少行写一次?我们下面来看一下它的相关实现。


一:日志参数

1.muduo的日志滚动

(1)文件大小,例如每天写满1G换下一个文件

(2)时间(每天零点新建一个日志文件,不论前一个文件是否写满)

2.典型日志文件名

logfile_test.20130411-115604.popo.7743.log


二:LogFile类

构造参数: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_;   //文件智能指针


三:AppendFile类

成员变量:
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方法,供外部将小文件中的内容转化为字符串。


FileUtili.cpp

#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);
上面四种显示实例化方法都是有效的,采用模板显示实例化可以减小编译器实例化展开的开销。


你可能感兴趣的:(Muduo源码剖析,muduo源码剖析)