muduo源码阅读(五):日志库分析(一)

muduo日志库分析(一)

muduo的日志库由**LogStream{.h,.cc}、Logging{.h, .cc}、LogFile{.h, .cc}、AsyncLogging{.h, .cc}**组成。这里主要说明一下,这些文件(主要是文件里面对应的类)之间是怎么关联,并协同工作的。
在这里插入图片描述
首先各个类之间的关系如图所示,是相互包含的。

LogStream类里面有一个Buffer成员(一个模板类FixBuffer,并非muduo::Buffer类)。该类主要负责将要记录的日志内容放到这个Buffer里面。包括字符串,整型、double类型(整型和double要先将之转换成字符型,再放到buffer里面)。该类对这些类型都重载了<<操作符。这个LogStream类不做具体的IO操作。以后要是需要这个buffer里的数据,可以调用LogStream的buffer()函数,这个函数返回const Buffer&

Logging.h文件定义了logger类,而非Logging类。 Logger类用枚举类型定义了日志等级。

enum LogLevel
 {
   TRACE,
   DEBUG,
   INFO,
   WARN,
   ERROR,
   FATAL,
   NUM_LOG_LEVELS
 };

在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_TRACE<<"the server has read the client message";

细看这些宏定义,就可以发现其等价于

muduo::Logger(__FILE__,__LINE__,muduo::Logger::TRACE,__func__).stream()<<"the server has read the client message";

而Logger类返回的stream是一个LogStream对象,于是通过这些宏进行的日志记录将存放到LogStream对象中的一个buffer里面。前面已经说到LogStream函数并不进行实际的IO操作,所以写入LogStream的数据通过buffer()函数获取。

    Logger类还定义了两个函数指针,用于设置日志的输出位置。因为这种设置是基于类的,而不是基于对象,所以自然其有两个静态变量g_output,g_flush分别存放 输出函数和 刷新函数。
typedef void (*OutputFunc)(const char* msg, int len);
typedef void (*FlushFunc)();

默认情况下,其是向stdout(即标准输出)进行输出的。

void defaultOutput(const char* msg, int len)
{
  size_t n = fwrite(msg, 1, len, stdout);
  //FIXME check n
  (void)n;
}
 
void defaultFlush()
{
  fflush(stdout);
}
 
Logger::OutputFunc g_output = defaultOutput;
Logger::FlushFunc g_flush = defaultFlush;

此外,这个类还提供了两个静态函数,用于设置静态变量g_output和g_flush的。

static void setOutput(OutputFunc);
static void setFlush(FlushFunc); 

不用说就能感觉到 这两个函数很有用,因为其可以修改输出函数,进而重定向日志输出,我们可以将之修改为自己写的输出函数,在这个输出函数中,可以向我们想要输出的缓存或者文件进行输出。在muduo中,一般是将日志重定向到AsyncLogging对象中,具体是:先存放到AsyncLogging对象的一个缓存中,然后由AsyncLogging进行异步写入文件中。

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

Logger类是间接进行IO的。Logger的析构函数如下:

Logger::~Logger()
{
  impl_.finish();//只是在buffer中添加文件名和行数。
  constLogStream::Buffer& buf(stream().buffer());
 
 g_output(buf.data(), buf.length());
  if (impl_.level_== FATAL)
  {
    g_flush();
    abort();
  }
}

在析构函数中,会调用输出函数g_output,把日志数据(已经存放于buf中)输出。

现在来说一下Logger类和LogStream类是怎么配合工作的。
使用LOG_*之类的宏会创建一个临时匿名Logger对象,这个对象有一个Impl对象,而Impl对象有一个LogStream对象。LOG_*宏会返回一个LogStream对象的引用。用于将内容输入到LogStream中的一个buffer中。在Logger的析构函数中,将存于LogStream的buffer的日志内容输出。
LOG_*的宏,创建一个临时匿名Logger对象,临时匿名很重要,因为临时匿名对象是一使用完就马上销毁,调用析构函数。而C++对于栈中的具名对象,先创建的后销毁。这就使得后创建的Logger对象先于先创建的Logger对象销毁。即先调用析构函数将日志输出,这就会使得日志内容反序(具体说是一个由{}包括的块中反序)。使用临时匿名Logger对象的效果就是:LOG_*这行代码不仅仅包含日志内容,还会马上把日志输出。

    至此,已经完成了一个基本的日志功能,不过还不是异步。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值