程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++的開源跨平台日志庫glog學習研究(二)--宏的使用

C++的開源跨平台日志庫glog學習研究(二)--宏的使用

編輯:C++入門知識

1. 日志輸出宏   這裡我們以一條最簡單的日至輸出為例說明:   LOG(WARNING) << "This is a warning message"; 這裡LOG是一個宏,其定義如下(logging.h line 487):   #define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream() 這裡根據LOG宏中的severity的不同有分別擴展成了另外四個宏,其中severity 有四個預定義(log_severity.h  line 51-59),分別代表不同級別的日志輸出,有INFO、WARNING、ERROR、FATAL,以WARNING為例,LOG(WARNING)被擴展為COMPACT_GOOGLE_LOG_WARNING.stream()。其中COMPACT_GOOGLE_LOG_ WARNING又是另外一個宏(logging.h line 391):   復制代碼 1 #if GOOGLE_STRIP_LOG <= 1 2 #define COMPACT_GOOGLE_LOG_WARNING google::LogMessage( \ 3       __FILE__, __LINE__, google::GLOG_WARNING) 4 #define LOG_TO_STRING_WARNING(message) google::LogMessage( \ 5       __FILE__, __LINE__, google::GLOG_WARNING, message) 6 #else 7 #define COMPACT_GOOGLE_LOG_WARNING google::NullStream() 8 #define LOG_TO_STRING_WARNING(message) google::NullStream() 9 #endif 復制代碼 到這裡基本就能看出門道了,google::LogMessage和google::NullStream都是類,根據GOOGLE_STRIP_LOG的不同定義,COMPACT_GOOGLE_LOG_ WARNING被定義為LogMessage或者NullStream, NullStream比較簡單,從名字上也能測到它就是一個無輸出的流(僅僅重載了operator <<,但實際上並不輸出任何信息),用以實現某些level的日志信息不被顯式輸出)。這裡主要看LogMessage。   此時根據文件名, 行號, 日志級別構造一個LogMessage類對象(logging.cc line 1153):   LogMessage::LogMessage(const char* file, int line, LogSeverity severity) : allocated_(NULL) {   Init(file, line, severity, &LogMessage::SendToLog); } LogMessage有很多重載構造,這裡不再一一列舉了。注意構造裡的初始化函數Init,除了文件名, 行號, 日志級別,還多了一個參數,Init聲明如下:   void Init(const char* file, int line, LogSeverity severity, void (LogMessage::*send_method)()); 即最後一個參數是一個函數指針,且可配置,用以設置真正的日志輸出,比如輸出到文件、控制台等,甚至有可能配置成輸出到遠程網絡端。Init內部用以初始化日志輸入的流緩沖區,初始化日志創建時間,格式,確定打印日志文件名等等。   此時一個完整的LogMessage的對象就創建並初始化完成了,回到LOG(severity)宏定義處,此時LOG宏可以表示成如下定義:   #define LOG(severity) google::LogMessage().stream() 也即是最終被展開為google::LogMessage類的成員函數stream()的返回值,stream()實現如下:   std::ostream& LogMessage::stream() {   return data_->stream_; } data_->stream_是一個LogStream對象,其定義如下:   復制代碼 1 class GOOGLE_GLOG_DLL_DECL LogStream : public std::ostream { 2   public: 3     LogStream(char *buf, int len, int ctr); 4   //..............此處省略 5   private: 6     base_logging::LogStreamBuf streambuf_; 7     int ctr_;  // Counter hack (for the LOG_EVERY_X() macro) 8     LogStream *self_;  // Consistency check hack 9 }; 復制代碼 上面所提及的google::NullStream即是繼承自LogStream,所以也是一個std::ostream對象。   至此一個日志輸出語句,   LOG(WARNING) << "This is a warning message"; 即可以表示為:   google:: LogStream() << "This is a warning message"; 到這裡就會發現這個和我們熟悉的cout輸出是一樣的了:   std::cout << "This is a warning message"; 一個google:: LogStream對象和std::cout都是std::ostream對象。   從上面也可以看出,每一次輸出一條日志信息都要創建一個google::LogMessage對象,在每次輸出結束後釋放LogMessage對象,在其析構函數中有如下代碼:   1 LogMessage::~LogMessage() { 2   Flush(); 3   delete allocated_; 4 } Flush成員函數即是刷新日志緩存區,相當於C++中流操作的flush或者C中文件操作的fflush。另外注意Flush實現裡有如下代碼:   復制代碼 1 //...... 2   { 3     MutexLock l(&log_mutex); 4     (this->*(data_->send_method_))(); 5     ++num_messages_[static_cast<int>(data_->severity_)]; 6   } 7 //...... 復制代碼 這是為了保證多個日志同時向同一介質進行輸出時到保持有序。注意鎖的使用前後有{}包圍。呵呵,這種用法其實我也偶爾使用,好處就是在一個比較大的語句塊中創建一個作用域更小的對象,這樣能使該對象及早釋放,避免和整個語句塊使用同一作用域。比如上面代碼中的在加鎖時使用了一個更小的作用域,該作用域結束後鎖就會立刻釋放,而不是等到Flush函數返回時才釋放,這樣就進一步提高了響應時間(其實這裡也有別的做法,比如我之前寫的文章:do{...}while(0)的妙用)。   到此一條日志輸出就算完成了,其他宏像DLOG、VLOG、VLOG_IF(帶條件檢測的輸出)都是按這種思路展開的,不再一一介紹了。   2. CHECK_XX宏   我個人感覺這類CHECK_XX宏比上面的LOG宏實現的還要隱晦難懂,當然設計的還是很巧妙的,值得學習一下,我嘗試做個分析。   在測試工程的logging_unittest.cc文件line 535的TestCHECK()函數中有如下代碼:   1 CHECK_NE(1, 2); 2 CHECK_GE(1, 1); 3 CHECK_GE(2, 1); 4 CHECK_LE(1, 1); 5 CHECK_LE(1, 2); 這些宏從名字上也能猜到是干嘛用的,他們的定義如下:   1 #define CHECK_EQ(val1, val2) CHECK_OP(_EQ, ==, val1, val2) 2 #define CHECK_NE(val1, val2) CHECK_OP(_NE, !=, val1, val2) 3 #define CHECK_LE(val1, val2) CHECK_OP(_LE, <=, val1, val2) 4 #define CHECK_LT(val1, val2) CHECK_OP(_LT, < , val1, val2) 5 #define CHECK_GE(val1, val2) CHECK_OP(_GE, >=, val1, val2) 6 #define CHECK_GT(val1, val2) CHECK_OP(_GT, > , val1, val2) 其中CHECK_OP宏定義如下:   #define CHECK_OP(name, op, val1, val2) \   CHECK_OP_LOG(name, op, val1, val2, google::LogMessageFatal) 而CHECK_OP_LOG宏定義如下:   復制代碼 1 typedef std::string _Check_string; 2 #define CHECK_OP_LOG(name, op, val1, val2, log)          \ 3   while (google::_Check_string* _result =                \ 4          google::Check##name##Impl(                      \ 5              google::GetReferenceableValue(val1),        \ 6              google::GetReferenceableValue(val2),        \ 7              #val1 " " #op " " #val2))                   \ 8     log(__FILE__, __LINE__,                              \ 9         google::CheckOpString(_result)).stream() 復制代碼 接下來我們以CHECK_LE(1, 2);為例,將其逐步擴展:   復制代碼  1  CHECK_LE(1, 2)    2  ------> CHECK_OP(_LE, <=, 1, 2)    3  ------> CHECK_OP_LOG(_LE, <=, 1, 2, google::LogMessageFatal)   4  ------> #define CHECK_OP_LOG(_LE, <=, 1, 2, google::LogMessageFatal)                         \  5   while (std::string * _result =  \  6          google::Check_LEImpl(    \  7              1,                   \  8              2,                   \  9              "1 <= 2"))           \ 10     log(__FILE__, __LINE__,       \ 11         google::CheckOpString(_result)).stream() 復制代碼 其中google::Check_LEImpl也是通過宏預先實現的,這個宏就是DEFINE_CHECK_OP_IMPL(Check_LE, <=):   復制代碼  1 #define DEFINE_CHECK_OP_IMPL(name, op) \  2   template <typename T1, typename T2>  \  3   inline std::string* name##Impl(const T1& v1, const T2& v2,    \  4                             const char* exprtext) {  \  5     if (GOOGLE_PREDICT_TRUE(v1 op v2)) return NULL;  \  6     else return MakeCheckOpString(v1, v2, exprtext); \  7   } \  8   inline std::string* name##Impl(int v1, int v2, const char* exprtext) { \  9     return name##Impl<int, int>(v1, v2, exprtext); \ 10   } 復制代碼 展開後就實現了google::Check_LEImpl函數(其他與此類似,這裡只以“<=”為例說明):   CHECK_LE(1, 2) ------>   while (std::string * _result = google::Check_LEImpl(1, 2, "1 <= 2"))       log(__FILE__, __LINE__,google::CheckOpString(_result)).stream() 其中google::Check_LEImpl又調用了模板實現的Check_LEImpl,該函數根據兩個參數v1、v2和操作符op決定了要麼返回NULL,要麼返回一個string*,如果返回NULL,則不再執行下面的輸出,否則則輸出日志信息。   其中宏GOOGLE_PREDICT_TRUE、內聯函數GetReferenceableValue、函數MakeCheckOpString、函數CheckOpString、結構體CheckOpString都是比較簡單的,就不再講了。   至此,就完成了CHECK_LE(1, 2)的擴展,如果檢測為true,則返回NULL,否則就會返回一個有明確提示信息的字符串指針,並輸出該信息,然後是程序宕掉。   比如如果驗證CHECK_LE(1, 0),因為為false,則觸發日志輸出:   F0503 17:39:09.961318  4232 logging_unittest.cc:203] Check failed: 1 <= 0 (1 vs. 0) *** Check failure stack trace: *** 然後程序異常退出。   3. 在宏中使用do-while(0)   比如在logging.h的817行有如下宏定義:   1 #define CHECK_DOUBLE_EQ(val1, val2)              \ 2   do {                                           \ 3     CHECK_LE((val1), (val2)+0.000000000000001L); \ 4     CHECK_GE((val1), (val2)-0.000000000000001L); \ 5   } while (0) 這裡主要關注do-while(0)的使用,恰巧我之前也寫過一篇文章介紹do-while(0)的妙用,請看:do{...}while(0)的妙用,這裡不再多言了。   4. 使用宏進行全局初始化   請看下面的宏用法:   REGISTER_MODULE_INITIALIZER(utilities, yUserNameInitializer()); REGISTER_MODULE_INITIALIZER在googleinit.h的44行定義:   1 #define REGISTER_MODULE_INITIALIZER(name, body)                 \ 2   namespace {                                                   \ 3     static void google_init_module_##name () { body; }          \ 4     GoogleInitializer google_initializer_module_##name(#name,   \ 5             google_init_module_##name);                         \ 6   } 其中類GoogleInitializer的實現如下:   復制代碼 1 class GoogleInitializer { 2  public: 3   typedef void (*void_function)(void); 4   GoogleInitializer(const char*, void_function f) { 5     f(); 6   } 7 }; 復制代碼 這個比較簡單,其實就是做一些比如注冊、全局初始化的工作,相應的也可以設置對應的宏用於做程序退出時的清理工作。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved