muduo::FileUtil、LogFile分析

FileUtil

这个类负责把日志写入到文件
FileUtil.h

namespace FileUtil
{

// read small file < 64KB
class ReadSmallFile : boost::noncopyable
{
 public:
  ReadSmallFile(StringArg filename);
  ~ReadSmallFile();

  // return errno
  template<typename String>
  int readToString(int maxSize,
                   String* 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<typename String>
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);
}

// 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_;//已经使用了多少
};
}

FileUtil.cc

FileUtil::AppendFile::AppendFile(StringArg filename)
  : fp_(::fopen(filename.c_str(), "ae")),  // 'e' for O_CLOEXEC
    writtenBytes_(0)
{
  assert(fp_);
  ::setbuffer(fp_, buffer_, sizeof buffer_);
  // 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)
  {
    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_);
}

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
  }
}

// return errno
template<typename String>
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;
      if (::fstat(fd_, &statbuf) == 0)
      {
        if (S_ISREG(statbuf.st_mode))
        {
          *fileSize = statbuf.st_size;
          content->reserve(static_cast<int>(std::min(implicit_cast<int64_t>(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<size_t>(maxSize))
    {
      size_t toRead = std::min(implicit_cast<size_t>(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);
    if (n >= 0)
    {
      if (size)
      {
        *size = static_cast<int>(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*);

LogFile

LogFile类负责日志的滚动,例如日大小达到指定大小、到达某一个时间点都会新建一个日志。
日志的名字为:日志名+日期+时间+主机名+线程ID+.log

LogFile.h

namespace FileUtil
{
class AppendFile;//前置声明
}

class LogFile : boost::noncopyable
{
 public:
  LogFile(const string& basename,
          size_t rollSize,
          bool threadSafe = true,//是否是线程安全。所以下面才有了unlocked版本的函数
          int flushInterval = 3,
          int checkEveryN = 1024);
  ~LogFile();

  void append(const char* logline, int len);
  void flush();//输出到日志文件
  bool rollFile();

 private:
  void append_unlocked(const char* logline, int len);

  static string getLogFileName(const string& basename, time_t* now);//获取文件名字

  const string basename_;//文件名字
  const size_t rollSize_;//超过这个大小,则回滚文件
  const int flushInterval_;//写入日志间隔
  const int checkEveryN_;//没写入这么多条日志,检查一次是否要回滚

  int count_;//写入日志次数,配合checkEveryN_

  boost::scoped_ptr<MutexLock> mutex_;
  time_t startOfPeriod_;//开始写入日志事件
  time_t lastRoll_;//上一次滚动日志事件
  time_t lastFlush_;//上一次写入日志时间
  boost::scoped_ptr<FileUtil::AppendFile> file_;//文件相关的类,辅助写入到文件

  const static int kRollPerSeconds_ = 60*60*24;//每隔多久回滚一次日志
};

LogFile.cc

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();//先回滚一次,在回滚中才会初始化file_。当file_.reset时,前一次的会在析构函数关闭
}

LogFile::~LogFile()
{
}

void LogFile::append(const char* logline, int len)
{
  if (mutex_)//线程安全
  {
    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);

  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_)
      {
        rollFile();
      }
      else if (now - lastFlush_ > flushInterval_)
      {
        lastFlush_ = now;
        file_->flush();
      }
    }
  }
}

bool 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 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 ?
  strftime(timebuf, sizeof timebuf, ".%Y%m%d-%H%M%S.", &tm);
  filename += timebuf;

  filename += ProcessInfo::hostname();

  char pidbuf[32];
  snprintf(pidbuf, sizeof pidbuf, ".%d", ProcessInfo::pid());
  filename += pidbuf;

  filename += ".log";

  return filename;
}

写一个测试程序:

#include <muduo/base/LogFile.h>
#include <muduo/base/Logging.h>

#include <string>

boost::scoped_ptr<muduo::LogFile> g_logFile;//全局变量,输出到这里

void outputFunc(const char *msg, int len)
{
    g_logFile->append(msg, len);
}

void flushFunc()
{
    g_logFile->flush();
}

int main(int argc, char *argv[])
{

    muduo::Logger::setOutput(outputFunc);
    muduo::Logger::setFlush(flushFunc);
    const std::string  logName="LogFile";
    g_logFile.reset(new muduo::LogFile(logName.c_str(), 150 * 1024));//100k


    muduo::string logContent = "This is a test. This is a test.  This is a test.  This is a test.  This is a test.  This is a test.";

    for (int i = 0; i < 10000; ++i)
    {
        LOG_INFO << logContent << i;
        usleep(1000);
    }
}

输出为:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
package com.hexiang.utils; import java.io.*; /** * FileUtil. Simple file operation class. * * @author BeanSoft * */ public class FileUtil { /** * The buffer. */ protected static byte buf[] = new byte[1024]; /** * Read content from local file. FIXME How to judge UTF-8 and GBK, the * correct code should be: FileReader fr = new FileReader(new * InputStreamReader(fileName, "ENCODING")); Might let the user select the * encoding would be a better idea. While reading UTF-8 files, the content * is bad when saved out. * * @param fileName - * local file name to read * @return * @throws Exception */ public static String readFileAsString(String fileName) throws Exception { String content = new String(readFileBinary(fileName)); return content; } /** * 读取文件并返回为给定字符集的字符串. * @param fileName * @param encoding * @return * @throws Exception */ public static String readFileAsString(String fileName, String encoding) throws Exception { String content = new String(readFileBinary(fileName), encoding); return content; } /** * 读取文件并返回为给定字符集的字符串. * @param fileName * @param encoding * @return * @throws Exception */ public static String readFileAsString(InputStream in) throws Exception { String content = new String(readFileBinary(in)); return content; } /** * Read content from local file to binary byte array. * * @param fileName - * local file name to read * @return * @throws Exception */ public static byte[] readFileBinary(String fileName) throws Exception { FileInputStream fin = new FileInputStream(fileName); return readFileBinary(fin); } /** * 从输入流读取数据为二进制字节数组. * @param streamIn * @return * @throws IOException */ public static byte[] readFileBinary(InputStream streamIn) throws IOException { BufferedInputStream in = new BufferedInputStream(streamIn); ByteArrayOutputStream out = new ByteArrayOutputStream(10240); int len; while ((len

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值