muduo的日志库分析二之Logger类

github地址:

https://github.com/chenshuo/muduo/blob/master/muduo/base/Logging.h

https://github.com/chenshuo/muduo/blob/master/muduo/base/Logging.cc

Logger类图

muduo的日志库分析二之Logger类_第1张图片
muduo的日志库分析二之Logger类_第2张图片
Logger使用时序图如下:
muduo的日志库分析二之Logger类_第3张图片

#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) \
  muduo::Logger(__FILE__, __LINE__).stream()

LOG_INFO<<“info ...”; // 使用方式
现在来说一下Logger类和LogStream类是怎么配合工作的。
      使用LOG_*之类的宏会创建一个临时匿名Logger对象,这个对象有一个Impl对象,而Impl对象有一个LogStream对象。LOG_*宏会返回一个LogStream对象的引用。用于将内容输入到LogStream中的一个buffer中。在Logger的析构函数中,调用 g_output,即 g_output(buf.data(), buf.length()),将存于LogStream的buffer的日志内容输出。如果是FATAL错误,还要调用g_flush,最后abort()程序。如果没有调用g_flush,会一直输出到缓冲区(标准输出缓冲区,文件FILE缓冲区)满才会真的输出在标准输出,或者写入到文件中去。

Logger::~Logger()
{
  impl_.finish();
  const LogStream::Buffer& buf(stream().buffer());  // 获取缓冲区
  g_output(buf.data(), buf.length());  // 默认输出到stdout

  // 当日志级别为FATAL时,flush设备缓冲区并终止程序
  if (impl_.level_ == FATAL) 
  {
    g_flush();
    abort();
  }
}
    注:可以使用setvbuf设置缓冲区的大小。

 int setvbuf ( FILE * stream, char * buffer, int mode, size_t size );
   默认日志信息输出到标准输出(g_output = defaultOutput、g_flush = defaultFlush),也可以输出到文件,使用SetOutput、SetFlush 设置。
        LOG_*的宏,创建一个临时匿名Logger对象,临时匿名很重要,因为临时匿名对象是一使用完就马上销毁,调用析构函数。而C++对于栈中的具名对象,先创建的后销毁。这就使得后创建的Logger对象先于先创建的Logger对象销毁。即先调用析构函数将日志输出,这就会使得日志内容反序(具体说是一个由{}包括的块中反序)。使用临时匿名Logger对象的效果就是:LOG_*这行代码不仅仅包含日志内容,还会马上把日志输出。

      LOG_*之类的宏,先构造临时匿名Logger对象,muduo::Logger(__FILE__, __LINE__),其中__FILE__返回的是文件所在的绝对路径,Logger的嵌套类SourceFile,SourceFile类是用来截取绝对路径最后面的文件名。

//__FILE__ 日志记录的信息原文件  
class SourceFile  
{  
    public:  
    template   
    inline SourceFile(const char (&arr)[N])  
    : data_(arr),  
    size_(N-1)  
    {  
        const char* slash = strrchr(data_, '/');//返回最后一个 '/'  
        //截取的文件名  
        if(slash)  
        {  
            data_ = slash + 1;  
            size_ = static_cast(data_ - arr ); 
        }  
    }  
    // muduo::Logger(__FILE__, __LINE__).stream() 调用第二个构造函数char *的  
    explicit SourceFile(const char* filename)  
        : data_(filename)  
        {  
            const char* slash = strrchr(filename, '/');  
            if(slash)  
                data_ = slash + 1;  
            size_ = static_cast(strlen(data_));  
        }  
    const char* data_;  
    int size_;  
};  

 Logger类内部定义了一个私有的Impl类。这个Impl类有一个LogStream类。同大多数Impl方法一样,Logger类的具体操作由这个Impl类来完成。不过,Logger类中的Impl成员不是一个指针。使用Impl方法的一大原因是为了闭源,不仅仅隐藏实现代码还隐藏类的私有成员函数。但对于muduo这个开源库来说,这没有意义。而且使用指针的话,new的时候需要在堆中申请空间,这无疑会降低运行速度。

//Loger类的内部嵌套类
//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_;//Timestamp时间戳
  LogStream stream_;//LogStream类对象成员
  LogLevel level_;//日志级别
  int line_;//行号
  SourceFile basename_;
};
//Impl类的构造函数
//级别,错误(没有错误则传0),文件,行
//Impl类主要是负责日志的格式化
Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line)
  : time_(Timestamp::now()),//登记当前的时间
    stream_(),//LogStream类
    level_(level),//级别
    line_(line),//行
    basename_(file)//文件名称
{
  formatTime();//格式化时间
  CurrentThread::tid();//缓存当前线程的id
  //输出字符串格式的线程id
  stream_ << T(CurrentThread::tidString(), 6);
  stream_ << T(LogLevelName[level], 6);
  if (savedErrno != 0)//如果savedErrno不为零,还要输出对应的信息
  {
    stream_ << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";
  }
}

void Logger::Impl::formatTime()
{
  int64_t microSecondsSinceEpoch = time_.microSecondsSinceEpoch();//获得微秒格式的时间
  time_t seconds = static_cast(microSecondsSinceEpoch / 1000000);//获得秒
  int microseconds = static_cast(microSecondsSinceEpoch % 1000000);//微秒
  if (seconds != t_lastSecond)
  {
    t_lastSecond = seconds;
    struct tm tm_time;
    ::gmtime_r(&seconds, &tm_time); // FIXME TimeZone::fromUtcTime

    int len = snprintf(t_time, sizeof(t_time), "%4d%02d%02d %02d:%02d:%02d",
        tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
        tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
    assert(len == 17); (void)len;
  }
  Fmt us(".%06dZ ", microseconds);
  assert(us.length() == 9);
  //LogStream类重载运算符,把信息输出到缓冲区
  stream_ << T(t_time, 17) << T(us.data(), 9);
}







你可能感兴趣的:(muduo)