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_; } } }