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)

image
主要做了一些<<运算符的重载,将数据内容先放进自定义的缓冲区buffer中。
如:

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

FixedBuffer类(LogStream.h)

缓冲区类
image

有趣的是,这个模板类的参数不是类型,而是缓冲区的大小

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中实现
image

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);
}
posted @ 2021-05-13 10:56  零十  阅读(184)  评论(0编辑  收藏  举报