騰訊開源框架TarsCpp-rpc設計分析-server(六)

6 日誌類源碼解析

Tars的日誌部分是自己實現的,翻看了代碼,額,怎麼說,日誌部分的代碼對初學者有一點點不友好。

  • 日誌部分代碼不夠“單純”,為了支持遠程寫日誌,內嵌了一些“額外”的代碼。這樣不能直接把這部分代碼拿出來在別的項目使用(為了方便一些同學想單獨調試日誌部分代碼,筆者把那些“額外代碼”去掉了,做了一個可以單獨運行的模塊,你可以在這裡獲取代碼:Tars-log demo)
  • 日誌部分的類繼承關係要複雜一些,且涉及到std::basic_streambuf< char >的自定義實現,一不小心掉坑裡,可能就爬不出來了

本著“死貧道,不死道友”的精神,筆者對日誌類進行了簡要梳理,希望對想要了解Tars日誌類的同學有些幫助

6.1 找准入口

如果想自己先梳理邏輯,可以按照下面的代碼順序進行

<code>// setLogInfo中,初始化了線程池,放入具體任務,循環等待輸入內容
TarsRollLogger::getInstance()->setLogInfo(ServerConfig::Application, ServerConfig::ServerName, ServerConfig::LogPath, ServerConfig::LogSize, ServerConfig::LogNum, _communicator, ServerConfig::Log);

//設置日誌等級
TarsRollLogger::getInstance()->logger()->setLogLevel("DEBUG");

//放入具體內容, setLogInfo中等待的線程寫入文件
TarsRollLogger::getInstance()->logger()->debug() << "[TARS]" << "HelloWorld" << endl;/<code>

6.2 邏輯梳理

騰訊開源框架TarsCpp-rpc設計分析-server(六)

Tars日誌.jpg

這張圖畫的的確有點亂。。。請擔待

  • 日誌的入口類為TarsRollLogger,裡面有兩個最主要的類TC_LoggerThreadGroup和TC_Logger< RollWriteT, TC_RollBySize >,分別位於圖中左上角和最右邊
  • TC_LoggerThreadGroup中有一個TC_ThreadPool的線程池,線程池裡有一個ThreadWorker線程從_jobqueue中等待任務。
  • TC_LoggerThreadGroup::_LoggerThreadGroup::run中,一直在循環執行flush動作。flush在遍歷logger_set,每次遍歷會調用TC_LoggerRoll::flush(),將TC_ThreadQueue<pair> > _buffer中的內容寫入文件。由此可以看出,弄清Tars日誌工作機理,一是要追溯logger_set中的內容是從哪兒來的;二是_buffer中的內容是從哪兒來的,_buffer是logger_set的成員變量,所以先來看logger_set/<pair>
  • logger_set的線索是藍色框。我們已經對藍色框進行了編號,1號位於TC_Logger< RollWriteT, TC_RollBySize >類中(右上角)。
  • 先看下TC_Logger的繼承關係,TC_Logger繼承:RollWrapperI,:RollWrapperI繼承RollWrapperBase,1號中的setupThread具體實現就是在RollWrapperBase中,注意這裡setupThread是通過調用2號藍框中的_roll->setupThread實現的。而_roll是TC_RollBySize類(見綠色框),TC_RollBySize類繼承TC_LoggerRoll,所以_roll->setupThread具體實現是在3號藍色框中,即TC_LoggerRoll類中。
  • 在3號藍色框中可以比較清晰的看到是怎樣調用4號TC_LoggerThreadGroup::registerLogger方法,將TC_LoggerRoll放入logger_set中的
  • 繼續看_buffer(TC_ThreadQueue),它的線索是黃色框。核心思想是重要的事兒是析構函數乾的。
  • TC_Logger< RollWriteT, TC_RollBySize >在構造函數里偷偷幹了兩件事兒,一是將_roll(綠色框)作為變量傳入LoggerBuffer中(1號黃色框),二是將LoggerBuffer變量傳入了std::ostream _stream(2號黃色框)
  • 在進行“TarsRollLogger::getInstance()->logger()->debug()”的調用時,會調用3號黃色框LoggerStream stream(int level)方法,LoggerStream是一個臨時類,調用完debug函數會析構,析構時候調用4號黃色框中stream->flush,而stream->flush將調用LoggerBuffer的sync(5號黃色框)將內容寫入到_buffer(TC_ThreadQueue)中

貼一段驗證代碼,有助於理解5號黃色框__stream->flush調用LoggerBuffer的sync的邏輯

<code>#include <iostream>
#include <string>

using namespace std;

class LoggerBuffer : public std::basic_streambuf<char>
{
public:
LoggerBuffer(int num){cout<
~LoggerBuffer(){cout<
\t virtual int sync(){cout<
};

class TC_Logger
{
public:
TC_Logger()
:_buffer(1)
, _stream(&_buffer)
{}

\t~TC_Logger(){cout<
void stream(int level)
{
ostream *ost = NULL;
ost = &_stream;

//調用flush後會調用LoggerBuffer中的sync方法
\t\t ost->flush();
}

LoggerBuffer _buffer;
std::ostream _stream;
};

int main()
{
TC_Logger logger;
logger.stream(1);

\t cout<\t
return 0;
}/<char>/<string>/<iostream>/<code>

g++ main.cpp

執行./a.out,會看到執行過程

下面這張stream關係圖,對理解上面LoggerBuffer變量和ostream之間關係有些幫助

騰訊開源框架TarsCpp-rpc設計分析-server(六)

stream.jpg

6.3 小結

  • 線程池部分邏輯很清晰,接收任務,循環等待,常規套路
  • LoggerBuffer繼承了std::basic_streambuf< char >,重寫了sync方法。ostream類型變量在調用flush時候,會調用重寫的sync方法
  • Tars日誌不僅支持按大小寫入(TC_RollBySize),貌似也支持按時間寫入(TC_RollByTime),沒再仔細研究,感興趣的小夥伴可自行探索


分享到:


相關文章: