muduo網路庫學習筆記(8):高效日誌類的封裝
前言
在服務端程式設計中,日誌是必不可少的。
開發過程中,日誌的存在能方便我們除錯錯誤和更好地理解程式;執行過程中,日誌能幫助我們診斷系統故障並處理、記錄系統執行狀態。
muduo日誌類封裝細節
(1)日誌訊息有多種級別(level),如TRACE、DEBUG、INFO、WARN、ERROR、FATAL。日誌的輸出級別在執行時可調。
程式碼片段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輸出到日誌對應的裝置。
程式碼片段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類的使用示例:
#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:
程式碼片段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";
// (疑問:digits陣列為什麼是"9876543210123456789",而不直接賦為"0123456789"?)
// 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()完成對緩衝區的各項操作,例如:
程式碼片段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”。
程式碼片段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.日誌的滾動實現
程式碼片段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));
}
}
程式碼片段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_;
}
}
}
相關文章
- muduo網路庫學習筆記(1):Timestamp類筆記
- muduo網路庫學習筆記(3):Thread類筆記thread
- muduo網路庫學習筆記(12):TcpServer和TcpConnection類筆記TCPServer
- muduo網路庫學習之Timestamp類、AtomicIntegerT 類封裝中的知識點封裝
- muduo網路庫學習之ThreadLocal 類、ThreadLocalSingleton類封裝知識點thread封裝
- muduo網路庫學習筆記(6):單例類(執行緒安全的)筆記單例執行緒
- muduo網路庫學習之BlockinngQueue類、ThreadPool 類、Singleton類封裝中的知識點BloCthread封裝
- muduo網路庫學習之MutexLock類、MutexLockGuard類、Condition類、CountDownLatch類封裝中的知識點MutexCountDownLatch封裝
- muduo網路庫學習筆記(2):原子性操作筆記
- muduo網路庫學習之muduo_http 庫涉及到的類HTTP
- muduo網路庫學習筆記(11):有用的runInLoop()函式筆記OOP函式
- muduo網路庫學習筆記(14):chargen服務示例筆記
- muduo網路庫學習之muduo_inspect 庫涉及到的類
- muduo網路庫學習之Logger類、LogStream類、LogFile類封裝中的知識點封裝
- muduo網路庫學習筆記(13):TcpConnection生命期的管理筆記TCP
- muduo網路庫學習筆記(10):定時器的實現筆記定時器
- muduo網路庫學習筆記(9):Reactor模式的關鍵結構筆記React模式
- muduo網路庫學習筆記(5):執行緒池的實現筆記執行緒
- muduo網路庫學習筆記(7):執行緒特定資料筆記執行緒
- muduo網路庫學習之Exception類、Thread 類封裝中的知識點(重點講pthread_atfork())Exceptionthread封裝
- muduo網路庫學習筆記(4):互斥量和條件變數筆記變數
- Laravel8學習筆記-日誌元件Laravel筆記元件
- muduo網路庫學習之EventLoop(四):EventLoopThread 類、EventLoopThreadPool 類OOPthread
- muduo網路庫Timestamp類
- muduo網路庫Exception異常類Exception
- muduo網路庫學習筆記(15):關於使用stdio和iostream的討論筆記iOS
- win8 學習筆記二 輸出日誌筆記
- muduo網路庫編譯安裝編譯
- Go學習筆記-Zap日誌Go筆記
- struts 日誌包(學習筆記)筆記
- 13封裝網路請求類庫封裝
- muduo網路庫學習之EventLoop(七):TcpClient、ConnectorOOPTCPclient
- muduo網路庫學習之EventLoop(一):事件迴圈類圖簡介和muduo 定時器TimeQueueOOP事件定時器
- java學習筆記--封裝的注意點Java筆記封裝
- muduo網路庫AtomicIntegerT原子整數類
- 封裝中的get、set方法-學習筆記封裝筆記
- redo日誌檔案學習筆記(一)筆記
- Vipper日誌庫的學習