muduo源码分析之logger日志
相关文件
muduo/base/Logging.h --Logger类的声明和LOG_INFO等宏定义
muduo/base/Logging.cc --Logger构造、析构等方法的实现
muduo/base/LogStream.h --FixedBuffer类和LogStream类
muduo/base/LogStream.cc --LogStream重载<<运算符
作用
- 开发时调试程序
- 运行时诊断系统故障、记录系统运行状态
使用
在muduo中日志分了以下这几种级别,默认级别为INFO,TRACE和DEBUG的日志不输出。
日志级别 | 表示 |
---|---|
TRACE | 指出比DEBUG粒度更细的信息事件 |
DEBUG | 指出细粒度事件,用于开发调试 |
INFO | 表明消息在粗粒度级别上突出强调应用程序的运行过程 |
WARN | 系统能正常运行,但可能会出现潜在错误 |
ERROR | 虽然发生错误事件,但不影响系统的继续运行 |
FATAL | 指出每个严重的错误事件将会导致应用程序的退出 |
明白了muduo的日志级别就可以使用日志了,使用muduo的日志十分简单
#include <muduo/base/Logging.h>
#include <errno.h>
using namespace muduo;
int main()
{
LOG_TRACE<<"trace ...";
LOG_DEBUG<<"debug ...";
LOG_INFO<<"info ...";
LOG_WARN<<"warn ...";
LOG_ERROR<<"error ...";
//LOG_FATAL<<"fatal ...";
errno = 12;
LOG_SYSERR<<"syserr ...";
LOG_SYSFATAL<<"sysfatal ...";
return 0;
}
/*
20210512 15:41:09.788451Z 9850 INFO info ... - test.cc:10
20210512 15:41:09.788492Z 9850 WARN warn ... - test.cc:11
20210512 15:41:09.788493Z 9850 ERROR error ... - test.cc:12
20210512 15:41:09.788494Z 9850 ERROR Cannot allocate memory (errno=12) syserr ... - test.cc:15
20210512 15:41:09.788498Z 9850 FATAL Cannot allocate memory (errno=12) sysfatal ... - test.cc:16
Aborted(core dumped)
*/
默认是输出到stdout,当然也可以输出到自定义文件
#include <muduo/base/Logging.h>
#include <errno.h>
#include <stdio.h>
using namespace muduo;
FILE* g_file;
void dummyOutput(const char* msg, int len)
{
if (g_file)
{
fwrite(msg, 1, len, g_file);
}
}
void dummyFlush()
{
fflush(g_file);
}
int main()
{
g_file = ::fopen("/tmp/muduo_log", "ae");
Logger::setOutput(dummyOutput);
Logger::setFlush(dummyFlush);
LOG_TRACE<<"trace ...";
LOG_DEBUG<<"debug ...";
LOG_INFO<<"info ...";
LOG_WARN<<"warn ...";
LOG_ERROR<<"error ...";
//LOG_FATAL<<"fatal ...";
errno = 13;
LOG_SYSERR<<"syserr ...";
//LOG_SYSFATAL<<"sysfatal ...";
::fclose(g_file);
return 0;
}
logger源码分析
所以调用 LOG_INFO<<" " 时发生了什么呢?
简单的说:LOG_INFO是宏定义,生成Logger类的临时对象,调用stream()方法返回其中的流对象stream,流对象stream重载了<<运算符,先将日志保存到其中的缓冲区FixedBuffer,当Logger临时对象销毁时,在析构函数中将缓冲区的输出到stdout或设置的文件。
下面按照这个流程看一下主要代码
log宏定义(Logging.h)
#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()
以LOG_DEBUG为例,logLevel()返回日志的级别,默认为INFO。大于TRACE和DEBUG,所以LOG_TRACE和LOG_DEBUG的日志默认不会输出。级别是Logger类内的枚举类型。
enum LogLevel
{
TRACE,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
NUM_LOG_LEVELS,
};
而
muduo::Logger(__FILE__, __LINE__, muduo::Logger::DEBUG, __func__)
生成Logger类的临时对象,参数__FILE__为源文件名,__LINE__为所在行,_func__为所在函数名,这些是编译器内置宏。临时对象调用stream()方法返回Logger类中嵌套类Impl的成员对象stream,stream_所属类为LogStream。
LogStream类(LogStream.h)
主要做了一些<<运算符的重载,将数据内容先放进自定义的缓冲区buffer中。
如:
self& operator<<(const string& v)
{
buffer_.append(v.c_str(), v.size());
return *this;
}
FixedBuffer类(LogStream.h)
缓冲区类
有趣的是,这个模板类的参数不是类型,而是缓冲区的大小
template<int SIZE>
class FixedBuffer : noncopyable
{
public:
FixedBuffer()
: cur_(data_)
{
setCookie(cookieStart);
}
~FixedBuffer()
{
setCookie(cookieEnd);
}
void append(const char* /*restrict*/ buf, size_t len)
{
// FIXME: append partially
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_; }
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_; //缓冲区内指针,指向未写区域的首地址
};
附加信息的写入
那时间戳、线程tid等信息是什么时候放进缓冲区的呢?
在Logger内嵌套类Impl构造时,构造函数在Logging.cc中实现
Logger::Impl::Impl(LogLevel level, int savedErrno, const SourceFile& file, int line)
: time_(Timestamp::now()),
stream_(),
level_(level),
line_(line),
basename_(file)
{
formatTime(); //写入时间戳,在formatTime方法中实现
CurrentThread::tid();
stream_ << T(CurrentThread::tidString(), CurrentThread::tidStringLength()); //写入tid
stream_ << T(LogLevelName[level], 6); //写入日志级别
if (savedErrno != 0)
{//如果Errno不为0的话,也写入
stream_ << strerror_tl(savedErrno) << " (errno=" << savedErrno << ") ";
}
}
上面tid和日志级别的写入为什么要放进T类中呢?T类是为了在编译时知道字符串长度。
// helper class for known string length at compile time
class T
{
public:
T(const char* str, unsigned len)
:str_(str),
len_(len)
{
assert(strlen(str) == len_);
}
const char* str_;
const unsigned len_;
};
inline LogStream& operator<<(LogStream& s, T v)
{
s.append(v.str_, v.len_);
return s;
}
inline LogStream& operator<<(LogStream& s, const Logger::SourceFile& v)
{
s.append(v.data_, v.size_);
return s;
}
日志的输出
先前说道,日志先保存到缓冲区中,当Logger类临时对象析构时才输出到stdout或文件
Logging.cc
Logger::~Logger()
{
impl_.finish();
const LogStream::Buffer& buf(stream().buffer());
g_output(buf.data(), buf.length());
if (impl_.level_ == FATAL)
{
g_flush();
abort();
}
}
默认的g_output和g_flush是stdout,设置成文件输出则给g_output/g_flush函数指针赋值即可
void defaultOutput(const char* msg, int len)
{
size_t n = fwrite(msg, 1, len, stdout);
//FIXME check n
(void)n;
}
void defaultFlush()
{
fflush(stdout);
}