zoukankan      html  css  js  c++  java
  • muduo日志库分析

    一、前言 在服务端编程中,日志是必不可少的。

    开发过程中,日志的存在能方便我们调试错误和更好地理解程序;运行过程中,日志能帮助我们诊断系统故障并处理、记录系统运行状态。

    二、muduo日志类封装细节 (1)日志消息有多种级别(level),如TRACE、DEBUG、INFO、WARN、ERROR、FATAL。日志的输出级别在运行时可调。

    [code]代码片段1:返回当前日志级别
    文件名:Logging.cc
    
    Logger::LogLevel initLogLevel()
    {
      if (::getenv("MUDUO_LOG_TRACE")) //获取环境变量MUDUO_LOG_TRACE
        return Logger::TRACE;
      else if (::getenv("MUDUO_LOG_DEBUG"))
        return Logger::DEBUG;
      else
        return Logger::INFO;
    }
    (2)日志类Logger的使用流程

    Logger使用时序图如下:

    Logger类主要负责日志的级别等,它的内部嵌套类Impl则负责实际的实现。使用时,首先构造一个匿名的Logger对象,然后调用stream()函数返回一个LogStream对象,LogStream对象再调用重载的<<运算符来输出日志。事实上,日志先输出到缓冲区,然后才输出到标准输出或文件。匿名的Logger对象在销毁时调用析构函数,析构函数调用g_output和g_flush输出到日志对应的设备。

    [code]代码片段2:Logger的析构函数
    文件名:Logging.cc
    
    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();
      }
    }
    Logger类的使用示例:
    [code]#define LOG_INFO if (muduo::Logger::logLevel() <= muduo::Logger::INFO) 
      muduo::Logger(__FILE__, __LINE__).stream()
    
    LOG_INFO<<“info ...”;   // 使用方式
    muduo::Logger(__FILE__, __LINE__).stream()<<“info”; //传递代码所在的文件名和行号参数
    (3)重载<<运算符

    以输出int类型的<<运算符为例,它并不是直接存放int类型的数据,而是转换为string类型后再存放到buffer:

    [code]代码片段3:重载<<运算符
    文件名:LogStream.cc
    
    ......
    
    //通过调用convert函数将整数转换为字符串
    template<typename T>
    size_t convert(char buf[], T value)
    {
      T i = value;
      char* p = buf;
    
      do
      {
        int lsd = static_cast<int>(i % 10); //得到最后一个数字,last digit
        i /= 10;
        /**
         * const char digits[] = "9876543210123456789";
         * const char* zero = digits + 9;
         * 
         * 假如此时获取的lsd值为5,指针zero指向digits[]中的'0'
         * zero[lsd]再偏移lsd即5个位置,便获取到了字符'5',保存到了buf中
         */
        *p++ = zero[lsd];
      } while (i != 0);
    
      //为负数则添加负号
      if (value < 0)
      {
        *p++ = '-';
      }
      *p = '';
      std::reverse(buf, p); //将字符串逆转
    
      return p - buf;
    }
    
    ......
    
    template<typename T>
    void LogStream::formatInteger(T v)
    {
      //kMaxNumericSize的值为32,即如果buffer的空间足够大
      if (buffer_.avail() >= kMaxNumericSize)
      {
        size_t len = convert(buffer_.current(), v);
        buffer_.add(len);
      }
    }
    
    ......
    
    LogStream& LogStream::operator<<(int v)
    {
      formatInteger(v); //调用formatInteger()函数
      return *this; //返回LogStream对象的指针
    }
    (4)FixedBuffer的设计

    FixedBuffer的实现为一个模板类,传入一个非类型参数SIZE表示缓冲区的大小。通过对data_首地址、cur_指针、end()函数的组合调用完成缓冲区的各操作,例如:

    [code]代码片段4:模版类FixedBuffer的成员函数avail()返回当前可用的空间
    文件名:LogStream.h
    
    int avail() const 
    { 
        return static_cast<int>(end() - cur_); 
    }
    (5)日志滚动

    muduo库日志滚动的条件通常有两个:

    文件大小 - 例如每写满1G换下一个文件

    时间 - 例如每天零点新建一个文件,不管前一个文件是否写满

    I.日志文件文件名的设计

    例:logfile_test.20120603-144022.hostname.3605.log

    第一部分如“logfile_test”是日志文件的basename;

    第二部分如“20120603-144022”是日志的创建时间(UTC时间);

    第三部分如“hostname”是主机名称;

    第四部分如“3605”是进程id;

    最后是日志后缀名“.log”。

    [code]代码片段5:获取日志文件名
    文件名:LogFile.cc
    
    string LogFile::getLogFileName(const string& basename, time_t* now)
    {
      string filename;
      //预留basename的size加上64字节的空间
      filename.reserve(basename.size() + 64);
      filename = basename;
    
      char timebuf[32];
      char pidbuf[32];
      struct tm tm;
      *now = time(NULL);
      gmtime_r(now, &tm); // 线程安全,获取日志创建时间
      strftime(timebuf, sizeof timebuf, ".%Y%m%d-%H%M%S.", &tm); //将时间格式化
      filename += timebuf;
      filename += ProcessInfo::hostname(); //用到了gethostname()返回主机名
      snprintf(pidbuf, sizeof pidbuf, ".%d", ProcessInfo::pid());
      filename += pidbuf;
      filename += ".log";
    
      return filename;
    }
    II.日志的滚动实现
    [code]代码片段6:日志的滚动
    文件名:LogFile.cc
    
    void LogFile::rollFile()
    {
      time_t now = 0;
      string filename = getLogFileName(basename_, &now);
    
      //注意,这里先除以kRollPerSeconds_ 后乘kRollPerSeconds_表示
      //对齐至kRollPerSeconds_(24*60*60)整数倍,也就是时间调整到当天零点。
      time_t start = now / kRollPerSeconds_ * kRollPerSeconds_;
    
      //如果now大于上一次滚动日志文件时间就滚动
      if (now > lastRoll_)
      {
        lastRoll_ = now; //lastRoll_是上一次滚动日志文件时间
        lastFlush_ = now; //lastFlush_是上一次日志写入文件时间
        startOfPeriod_ = start; //startOfPeriod_是开始记录日志时间(调整至零点的时间)
        file_.reset(new File(filename));
      }
    }
    [code]代码片段7:写入日志时,判断是否需要滚动日志
    文件名:LogFile.cc
    
    void LogFile::append_unlocked(const char* logline, int len)
    {
      file_->append(logline, len);
    
      //写入的字节数大于rollSize_时要滚动
      if (file_->writtenBytes() > rollSize_)
      {
        rollFile();
      }
      else
      { 
        //计数值count_超过kCheckTimeRoll_时也要判断是否需要滚动
        if (count_ > kCheckTimeRoll_)
        {
          count_ = 0;
          time_t now = ::time(NULL);
          time_t thisPeriod_ = now / kRollPerSeconds_ * kRollPerSeconds_;
          if (thisPeriod_ != startOfPeriod_)
          {
            rollFile();
          }
          //大于flush的间隔时间时则写入日志,不滚动
          else if (now - lastFlush_ > flushInterval_)
          {
            lastFlush_ = now;
            file_->flush();
          }
        }
        else
        {
          ++count_;
        }
      }
    }
     
  • 相关阅读:
    Python 操作Excel之通过xlutils实现在保留原格式的情况下追加写入数据
    【转载】Python字符串操作之字符串分割与组合
    【转】Python判断字符串是否为字母或者数字
    Appium 在测试android混合应用时,关于webview页面切换的那些事儿
    使用pip install XX 命令时报错
    Appium笔记(二) 丶Appium的安装
    Android SDK的下载与安装
    KlayGE 4.4中渲染的改进(五):OpenGL 4.4和OpenGLES 3
    最先进的开源游戏引擎KlayGE 4.4发布
    KlayGE 4.4中渲染的改进(四):SSSSS
  • 原文地址:https://www.cnblogs.com/ym65536/p/8291008.html
Copyright © 2011-2022 走看看