muduo的日志库分析二之Logger类
程序员文章站
2022-06-14 10:18: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类图
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);
}
推荐阅读