muduo日志-源码解析-自顶向下

简单介绍

muduo版本2.02

muduo的日志输出格式如下

LOG_SYSERR << "sockets::close";

与c++的流式输出类似。

输出样式如下

下面这个是运行ASyncLoggingtest的结果

muduo这个项目值得学习的地方很多,注释相比leveldb有点少

代码解析

ctrl + click可以知道,LOG_SYSERR是muduo定义的宏

代码如下

logger

LOG_DEBUG拿出来

首先判断设置的日志级别是否小于DEBUG,muduo::Logger::logLevel()返回的是定义在Logging.h中的全局变量g_logLevel(注意不是定义在类内)

下面是定义语句

如果判断为真的话,则构造一个muduo::Logger的匿名对象,并调用stream()函数。

换句话说,LOG_DEBUG这个宏相当于调用了三个函数

  1. muduo::Logger的构造函数
  2. muduo::Loggerstream()函数
  3. muduo::Logger的析构函数(匿名对象的生存期)

首先我们先聚焦muduo::Logger的构造函数的参数

  1. __FILE__ 是一个预处理器宏,这个宏会在编译时展开为一个字符串字面量,该字符串包含了当前源文件的名称(包括路径)
    2.__func__ 在编译时会被替换为一个字符串字面量,该字符串包含了当前函数的名称。
  2. __LINE__ 是一个预定义的宏,它会在编译时被替换为当前源代码行号的整数值
  3. muduo::Logger::DEBUG 是在Loggerenum

下面是muduo::Logger的构造函数的实现

Impl

其实又引出了impl_成员,他是muduo::Logger类内部的私有类,代码如下

它的作用就是输出LogLevel,错误码,文件名,行号,当前时间(formatTime()函数的功能)

SourceFile

其中SourceFile的作用是将 带路径的文件名 转换成 不带路径的文件名。

代码如下,中文注释为我添加

陈硕在他书中提到SourceFile提取文件名是在编译期计算。

回到正文,Impl类中的Timestamp 类维护了的时间点,用来获取当前时间。

Impl类的构造函数如下,我添加了中文注释

其中的类T是为了更好的输出,提供的指针和字符长度。

其中的formatTime函数用了缓存思想

在这里插入图片描述

其中的两个已经注释的变量是当前文件的线程局部变量,代码如下

对于为什么要有Impl类,首先是为了输出LogLevel,错误码,文件名,行号,当前时间,然后是为了代码复用,与内部私有函数的作用类似,如下图

我们回到Logger类,我们先跳过最重要的LogStream成员,先来了解Logger的析构函数做了什么。代码如下

简单来说,就是将写到LogStream缓冲区里面的内容,也发送到其他位置,默认是标准输出

g_output也是定义在该文件里的全局变量

通过setOutput()函数连接了AsyncLoggingLogger

具体来说

setOutput()替换成AsyncLoggingappend函数

LogStream

LogStream主要的工作就是重载<<,用以支持流式输出。

有一点我不是很明白,为什么不给所有的参数都加上const,这样既有原来不加const的功能,又能输出有顶层const限定的变量,还能输出常量。

LogStream的成员只有一个

buffer是一个可以指定大小的缓冲区

FixedBuffer

FixedBuffer的成员如下

看不出来cookie这个函数指针有什么用,欢迎指出

data_[SIZE]SIZE 可由模板指定,所以data_是可以指定大小的一段缓冲区

cur_指向缓冲区写入内容的末尾

好了,从LOG_DEBUG的接口开始我们已经深入到bottom了。

下一个问题是,这些buffer是如何写入到文件中。

ASyncLogging

下面是ASyncLogging的私有成员

最上面的threadFunc是线程的回调函数

首先分析唯一的构造函数

构造函数的参数与LogFile有关,LogFile不出现在asyncLogging的成员函数之中。在线程回调函数作为局部变量存在

其他的成员都是固定的

  • 初始化了线程,绑定了回调函数
  • 初始化了CountDownLatch,用来同步两个线程
  • 在堆内存中申请两个缓冲区,在线程回调函数中又申请了两个缓冲区

再来分析append函数

再来分析threadFunc函数,不好截图,直接粘贴代码块吧

void AsyncLogging::threadFunc() {
  assert(running_ == true);
  latch_.countDown();
  //局部变量LogFile
  LogFile output(basename_, rollSize_, false);
  //加上构造函数的两个buffer的话,就有四个buffer
  //append()函数里面的buffer不算,因为条件几乎达不到
  BufferPtr newBuffer1(new Buffer);
  BufferPtr newBuffer2(new Buffer);
  newBuffer1->bzero();
  newBuffer2->bzero();
  BufferVector buffersToWrite;
  buffersToWrite.reserve(16);
  while (running_) {
    assert(newBuffer1 && newBuffer1->length() == 0);
    assert(newBuffer2 && newBuffer2->length() == 0);
    assert(buffersToWrite.empty());

    //消费者生产者问题里面的消费者
    {
      muduo::MutexLockGuard lock(mutex_);
      //之所以用if而不用while是因为只有一个消费者
      if (buffers_.empty()) // unusual usage!
      {
        cond_.waitForSeconds(flushInterval_);
      }
      buffers_.push_back(std::move(currentBuffer_));
      currentBuffer_ = std::move(newBuffer1);
      buffersToWrite.swap(buffers_);
      if (!nextBuffer_) {
        nextBuffer_ = std::move(newBuffer2);
      }
    }

    assert(!buffersToWrite.empty());
    //如果前端堆积太多日志,将日志队列截断
    if (buffersToWrite.size() > 25) {
      char buf[256];
      snprintf(buf, sizeof buf,
               "Dropped log messages at %s, %zd larger buffers\n",
               Timestamp::now().toFormattedString().c_str(),
               buffersToWrite.size() - 2);
      fputs(buf, stderr);
      output.append(buf, static_cast<int>(strlen(buf)));
      buffersToWrite.erase(buffersToWrite.begin() + 2, buffersToWrite.end());
    }

    for (const auto &buffer : buffersToWrite) {
      // FIXME: use unbuffered stdio FILE ? or use ::writev ?
      output.append(buffer->data(), buffer->length());
    }

    if (buffersToWrite.size() > 2) {
      // drop non-bzero-ed buffers, avoid trashing
      buffersToWrite.resize(2);
    }

    if (!newBuffer1) {
      assert(!buffersToWrite.empty());
      newBuffer1 = std::move(buffersToWrite.back());
      buffersToWrite.pop_back();
      newBuffer1->reset();
    }

    if (!newBuffer2) {
      assert(!buffersToWrite.empty());
      newBuffer2 = std::move(buffersToWrite.back());
      buffersToWrite.pop_back();
      newBuffer2->reset();
    }

    buffersToWrite.clear();
    output.flush();
  }
  output.flush();
}

再来看一下latch_有什么用

主线程启动日志背景线程后阻塞

在线程启动后结束主线程阻塞

我认为是同步两个线程,防止主线程堆积太多日志,得不到日志线程的处理

LogFile

LogFile主要的功能就是周期性或达到一定条件后刷盘,滚动日志

LogFileASyncLogging中并不是以成员变量的身份出现,而是以日志线程的局部变量身份出现。

成员变量如下

append()flush()函数没什么好说的

构造函数

FileUtil

这个类就比较简单,简单的写入文件,统计写入的字节数。没什么好说的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值