muduo的日志库分析二之Logger类

时间:2021-05-22 19:49:43

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类
muduo的日志库分析二之Logger类
Logger使用时序图如下:
muduo的日志库分析二之Logger类

#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<int N>
inline SourceFile(const char (&arr)[N])
: data_(arr),
size_(N-1)
{
const char* slash = strrchr(data_, '/');//返回最后一个 '/'
//截取的文件名
if(slash)
{
data_ = slash + 1;
size_ = static_cast<int>(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<int>(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<time_t>(microSecondsSinceEpoch / 1000000);//获得秒  int microseconds = static_cast<int>(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);}