muduo日誌庫分析

1、前言 在服務端編程中,日誌是必不可少的。git

開發過程當中,日誌的存在能方便咱們調試錯誤和更好地理解程序;運行過程當中,日誌能幫助咱們診斷系統故障並處理、記錄系統運行狀態。編程

2、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使用時序圖以下:app

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:this

 

[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 = '\0';
  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_;
    }
  }
}
相關文章
相關標籤/搜索