上一篇從整個工程上簡單分析了glog,請看C++的開源跨平台日志庫glog學習研究(一),這一篇對glog的實現代碼入手,比如在其源碼中以宏的使用最為廣泛,接下來就先對各種宏的使用做一簡單分析。
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 };
這個比較簡單,其實就是做一些比如注冊、全局初始化的工作,相應的也可以設置對應的宏用於做程序退出時的清理工作。