muduo中的日志

日志的各个级别

这一篇写的特别好,https://blog.csdn.net/qq_28114615/article/details/103210148.
在这里插入图片描述

代码分析

SourceFile类

在logging.h中,声明了一个SourceFile类,这个类的作用就是给定一个字符串,把字符串中文件的文件名(basename)提取出来,就是类似于Python中的os.path.basename这个函数,该类具体为:

class SourceFile
  {
   public:
    template<int N>
    //两个构造函数,要么重载指针,要么重载引用
    SourceFile(const char (&arr)[N])
      : data_(arr),
        size_(N-1)
    {
      //获取具体的文件名
      const char* slash = strrchr(data_, '/'); // builtin function
      if (slash)
      {
        data_ = slash + 1;
        size_ -= static_cast<int>(data_ - arr);
      }
    }

    explicit SourceFile(const char* filename)
      : data_(filename)
    {
      const char* slash = strrchr(filename, '/');
      if (slash)
      {
        data_ = slash + 1;
      }
      size_ = static_cast<int>(strlen(data_));
    }

    const char* data_;
    int size_;
  };

类中只有两个构造函数,先说第二个,参数是const char *类型的,这里先简要说明下字符串数组和常量字符串的区别,先看下面两行代码

char *filename1 ="888/9991.cpp";
char filename2[] ="888/9992.cpp";

第一行表示char *类型的指针filename1指向一个字符串,指针存储的值代表这个地址,那么这个地址的指针就是"888/9991.cpp"存储的首地址,即&“888/9991.cpp”[0],这个表达式是可以实际运行的,C/C++语言中该字符串存储在全局区(静态区),第二行中filename2是一个字符串数组,filename2存储在栈区,filename2的大小就是字符串的长度+1,因为默认后面添加了一个’\0‘。
当数组作为形参的类型时,便会退化为指针,这是我们都知道的,但是如果退化为指针,这就意味着我们需要要么多传一个描述数组长度的参数,要么重新计算下数组的长度(仅对字符串数组而言),那么有没有便利的方法呢,那就是传引用,一个问题又来了,char [10] 的引用是char (&)[10],char [9] 的引用是char (&)[9],具有很大的变化性,因此可以采用模板中非类型参数推导实现,那就是SourceFile的第一个构造函数:

template<int N>
//两个构造函数,要么重载指针,要么重载引用
SourceFile(const char (&arr)[N])

由于数组中加了一个’\0’,所以长度比真实的多了1个,因此函数体内N-1,如此操作,当传入的是一个字符串数组时,我们就不用再算一下它的长度了,真的非常巧妙,再次见识到引用+模板的强大威力。

Impl类

class Impl
{
 public:
  typedef Logger::LogLevel LogLevel;
  Impl(LogLevel level, int old_errno, const SourceFile& file, int line);
  void formatTime();
  void finish();

  Timestamp time_;
  LogStream stream_;
  LogLevel level_;
  int line_;
  SourceFile basename_;
};

这里的Timestamp类不是很难,一个获取时间的类,里面包含各种字符串的转换等,这里需要介绍一个LogStream类,介绍LogStream类之前,FixedBuffer类也需要介绍一下:

template<int SIZE>
class FixedBuffer : noncopyable//不能复制
{
 public:
  FixedBuffer()
    : cur_(data_)//让cur_指向data_数组
  {
  //初始化,先设置回调函数为空,显然cookieStart只能是static
    setCookie(cookieStart);
  }

  ~FixedBuffer()
  {
    setCookie(cookieEnd);
  }

  void append(const char* /*restrict*/ buf, size_t len)
  {
    // FIXME: append partially
    //判断空间够不够,够的话将字符串复制到data_中(cur_后面)
    //当然这里,如果不够的话,默认是不处理的
    if (implicit_cast<size_t>(avail()) > len)
    {
      memcpy(cur_, buf, len);
      cur_ += len;
    }
  }

  const char* data() const { return data_; }
  //当前存储的字符串的长度
  int length() const { return static_cast<int>(cur_ - data_); }

  // write to data_ directly
  char* current() { return cur_; }
  //还剩多少空间,指针相减类型伪ptrdiff_t,转化为int类型
  int avail() const { return static_cast<int>(end() - cur_); }
  void add(size_t len) { cur_ += len; }

  void reset() { cur_ = data_; }
  void bzero() { memZero(data_, sizeof data_); }

  // for used by GDB
  const char* debugString();
  void setCookie(void (*cookie)()) { cookie_ = cookie; }
  // for used by unit test
  string toString() const { return string(data_, length()); }
  StringPiece toStringPiece() const { return StringPiece(data_, length()); }

 private:
  const char* end() const { return data_ + sizeof data_; }
  // Must be outline function for cookies.
  static void cookieStart();
  static void cookieEnd();

//明显是一个回调函数
  void (*cookie_)();
  char data_[SIZE];
  char* cur_;
};

这个类还好,可以说是一个简化版的vector,正式进入LogStream类:

class LogStream : noncopyable
{
  typedef LogStream self;
 public:
 //缓冲区的大小是4K
  typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer;

  self& operator<<(bool v)
  {
    buffer_.append(v ? "1" : "0", 1);
    return *this;
  }
//一系列的运算符重载,把输入转换成字符串加入缓冲区中
  self& operator<<(short);
  self& operator<<(unsigned short);
  self& operator<<(int);
  self& operator<<(unsigned int);
  self& operator<<(long);
  self& operator<<(unsigned long);
  self& operator<<(long long);
  self& operator<<(unsigned long long);

  self& operator<<(const void*);

  self& operator<<(float v)
  {
    *this << static_cast<double>(v);
    return *this;
  }
  self& operator<<(double);
  // self& operator<<(long double);

  self& operator<<(char v)
  {
    buffer_.append(&v, 1);
    return *this;
  }

  // self& operator<<(signed char);
  // self& operator<<(unsigned char);

  self& operator<<(const char* str)
  {
    if (str)
    {
      buffer_.append(str, strlen(str));
    }
    else
    {
      buffer_.append("(null)", 6);
    }
    return *this;
  }

  self& operator<<(const unsigned char* str)
  {
    return operator<<(reinterpret_cast<const char*>(str));
  }

  self& operator<<(const string& v)
  {
    buffer_.append(v.c_str(), v.size());
    return *this;
  }

  self& operator<<(const StringPiece& v)
  {
    buffer_.append(v.data(), v.size());
    return *this;
  }

  self& operator<<(const Buffer& v)
  {
    *this << v.toStringPiece();
    return *this;
  }

  void append(const char* data, int len) { buffer_.append(data, len); }
  const Buffer& buffer() const { return buffer_; }
  void resetBuffer() { buffer_.reset(); }

 private:
  void staticCheck();

  template<typename T>
  void formatInteger(T);

  Buffer buffer_;

  static const int kMaxNumericSize = 32;
};

依靠的类基本上介绍完毕,继续Logging.h:

extern Logger::LogLevel g_logLevel;

inline Logger::LogLevel Logger::logLevel()
{
  return g_logLevel;
}

根据外部的g_LogLevel设置Logger::logLevel(),下面的代码就是根据指定的g_logLevel来设置日志的级别了 下面的代码就是定义LOG_TRACE,LOG_DEBUG等临时对象,该对象属于LogStream类:

#define LOG_TRACE if (muduo::Logger::logLevel() <= muduo::Logger::TRACE) \
  muduo::Logger(__FILE__, __LINE__, muduo::Logger::TRACE, __func__).stream()
#define LOG_DEBUG if (muduo::Logger::logLevel() <= muduo::Logger::DEBUG) \
  muduo::Logger(__FILE__, __LINE__, muduo::Logger::DEBUG, __func__).stream()
#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \
  muduo::Logger(__FILE__, __LINE__).stream()
#define LOG_WARN muduo::Logger(__FILE__, __LINE__, muduo::Logger::WARN).stream()
#define LOG_ERROR muduo::Logger(__FILE__, __LINE__, muduo::Logger::ERROR).stream()
#define LOG_FATAL muduo::Logger(__FILE__, __LINE__, muduo::Logger::FATAL).stream()
#define LOG_SYSERR muduo::Logger(__FILE__, __LINE__, false).stream()
#define LOG_SYSFATAL muduo::Logger(__FILE__, __LINE__, true).stream()

const char* strerror_tl(int savedErrno);

LogFile类

AppendFile类

分析到上面,我们发现,上面这么多类实现的就是一个往缓冲区data_添加字符串的操作,没有涉及文件、输出这些,下面我们进行下面这部分的内容:

class AppendFile : noncopyable
{
 public:
  explicit AppendFile(StringArg filename);

  ~AppendFile();

  void append(const char* logline, size_t len);

  void flush();

  off_t writtenBytes() const { return writtenBytes_; }

 private:

  size_t write(const char* logline, size_t len);

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

StringArg类

一个简单的小类,目的就是传入字符串或者string对象时获取字符串指针

// For passing C-style string argument to a function.
class StringArg // copyable
{
 public:
  StringArg(const char* str)
    : str_(str)
  { }

  StringArg(const string& str)
    : str_(str.c_str())
  { }

  const char* c_str() const { return str_; }

 private:
  const char* str_;
};

扫清了上面一个障碍之后继续读代码,首先构造函数:

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

作用:使用标准的ISO C中的fopen打开(创建)一个文件,并将缓冲区设置为buffer_的大小(64KB)。这里没有用Linux中的open函数,因为带缓冲区,所以肯定不是线程安全的。
析构函数就是关闭这个文件指针,下面讲一下:

void FileUtil::AppendFile::append(const char* logline, const size_t len)
{
  size_t written = 0;
  while (written != len)
  {
    size_t remain = len - written;
    //注意:这里的write不是linux下的write,而是调用了自己写的函数
    size_t n = write(logline + written, remain);
    if (n != remain)
    {
      int err = ferror(fp_);
      if (err)
      {
        fprintf(stderr, "AppendFile::append() failed %s\n", strerror_tl(err));
        break;
      }
    }
    written += n;
  }
  writtenBytes_ += written;
}

AppendFile::write函数:

size_t FileUtil::AppendFile::write(const char* logline, size_t len)
{
  // #undef fwrite_unlocked
  return ::fwrite_unlocked(logline, 1, len, fp_);
}

由上可见,该函数为无锁写入一个文件fp_中,总结一下,AppendFile就是创建一个文件,设置缓冲区,并往里写字符串,下面开始正式介绍:

LogFile类

class LogFile : noncopyable
{
 public:
  LogFile(const string& basename,
          off_t rollSize,
          bool threadSafe = true,
          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_;
  //日志文件达到rollSize_后换一个新文件
  const off_t rollSize_;
  const int flushInterval_;
  const int checkEveryN_;

  int count_;

  std::unique_ptr<MutexLock> mutex_;
  //上一次记录日志的时间
  time_t startOfPeriod_;
  //上一次滚动日志的时间
  time_t lastRoll_;
  //上一次日志写入文件的时间
  time_t lastFlush_;
  std::unique_ptr<FileUtil::AppendFile> file_;

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

LogFile类中封装了一个AppendFile类,类中通过append等函数往append_file对象中添加字符串,添加的过程中对添加的日志进行计数,计时等,超过一定限制就重新生成一个日志文件。下面将结合一个muduo中给出的示例讲解一下如何实现一个完整的日志输出功能(LogFile_test.cc):

#include "muduo/base/LogFile.h"
#include "muduo/base/Logging.h"

#include <unistd.h>
//g_logFile所指的对象不能被复制
std::unique_ptr<muduo::LogFile> g_logFile;
//输出函数,将msg字符串添加到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[])
{
  char name[256] = { '\0' };
  strncpy(name, argv[0], sizeof name - 1);
  //一个LogFile对象
  g_logFile.reset(new muduo::LogFile(::basename(name), 200*1000));
  //这里将Logger中的回调函数设置为outputFunc
  muduo::Logger::setOutput(outputFunc);
  muduo::Logger::setFlush(flushFunc);

  muduo::string line = "1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ ";

  for (int i = 0; i < 10000; ++i)
  {
  	//LOG_INFO生成一个临时的Logger对象,该对象调用operator<<将line等变量
  	//放到内置的impl_.stream_中,该对象在析构的时候,一个完整的日志即被生
  	//成,这个日志将调用Logger类中的静态函数将日志输出。
  	//这里,g_logFile接住了这个输出,因此达到了最终的目的
    LOG_INFO << line << i;

    usleep(1000);
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值