Google glog 使用
Googleglog 庫實現了應用級的日志記錄,提供了C++ 風格的流操作和各種助手宏。
代碼示例:
#include <glog/logging.h>
int _tmain(int argc,_TCHAR* argv[])
{
google::InitGoogleLogging((const char *)argv[0]);
google::SetLogDestination(google::GLOG_INFO, "./myInfo");
LOG(WARNING) << "thisis the 1st warning!";
return 0;
}
“LOG”宏為日志輸出關鍵字,“INFO”為嚴重性程度。
主要支持功能如下:
1) 參數設置,以命令行啟動參數的方式設置標志參數來控制日志記錄行為;
2) 嚴重性分級,根據日志嚴重性分級記錄日志;
3) 可有條件地記錄日志信息;
4) 條件中止程序。豐富的條件判定宏,可預設程序終止條件;
5) 異常信號處理。程序異常情況,可自定義異常處理過程;
6) 支持debug功能。可只用於debug模式;
7) 自定義等級日志信息;
8) 原始日志記錄。無需加鎖與動態分配內存的輕量級線程安全版本;
9) 系統日志記錄;
10) google perror風格日志信息;
11) 日志信息移除。
glog的使用是比較簡單的,幾乎可以不用配置就直接使用了。在配置方式上,glog和一般的日志系統(如log4系列)是不太一樣的,后者一般使 用配置文件, 而glog是在命令行參數中指定的。對比優缺點,配置文件做的配置可能更加強大一些, 不過命令行配置雖然簡單但是也不失靈活。
glog可通過根據指定的嚴重性等級,來選擇性記錄日志。日志信息嚴重性等級按由低到高排列依次為:INFO,WARNING, ERROR, 和 FATAL四級。使用者可以在命令行中設置嚴重性等級門限值來控制日志的輸出,詳細見“參數設置”部分的“minloglevel”標志值的介紹。
其中FATAL等級的日志會在記錄以后終止程序運行,要謹慎使用。
這里我們以一條最簡單的日至輸出為例說明:
LOG(WARNING) << "Thisis a warning message";
這里LOG是一個宏,其定義如下(logging.hline 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.hline 391):
#if GOOGLE_STRIP_LOG <= 1
#define COMPACT_GOOGLE_LOG_WARNINGgoogle::LogMessage( \
__FILE__, __LINE__, google::GLOG_WARNING)
#define LOG_TO_STRING_WARNING(message)google::LogMessage( \
__FILE__, __LINE__, google::GLOG_WARNING, message)
#else
#define COMPACT_GOOGLE_LOG_WARNINGgoogle::NullStream()
#define LOG_TO_STRING_WARNING(message)google::NullStream()
#endif
到這里基本就能看出門道了,google::LogMessage和google::NullStream都是類,根據 GOOGLE_STRIP_LOG的不同定義,COMPACT_GOOGLE_LOG_WARNING被定義為LogMessage或者 NullStream,NullStream比較簡單,從名字上也能測到它就是一個無輸出的流(僅僅重載了operator<<,但實際上並 不輸出任何信息),用以實現某些level的日志信息不被顯式輸出)。這里主要看LogMessage。
此時根據文件名,行號, 日志級別構造一個LogMessage類對象(logging.ccline 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()
{
returndata_->stream_;
}
data_->stream_是一個LogStream對象,其定義如下:
class GOOGLE_GLOG_DLL_DECL LogStream : public std::ostream
{
public:
LogStream(char *buf, int len, int ctr);
//..............此處省略
private:
base_logging::LogStreamBufstreambuf_;
int ctr_; // Counter hack (for theLOG_EVERY_X() macro)
LogStream *self_; // Consistency check hack
};
上面所提及的google::NullStream即是繼承自LogStream,所以也是一個std::ostream對象。
至此一個日志輸出語句,
LOG(WARNING) << "Thisis a warning message";
即可以表示為:
google:: LogStream() << "Thisis a warning message";
到這里就會發現這個和我們熟悉的cout輸出是一樣的了:
std::cout << "Thisis a warning message";
一個google::LogStream對象和std::cout都是std::ostream對象。
從上面也可以看出,每一次輸出一條日志信息都要創建一個google::LogMessage對象,在每次輸出結束后釋放LogMessage對象,在其析構函數中有如下代碼:
LogMessage::~LogMessage() {
Flush();
deleteallocated_;
}
Flush成員函數即是刷新日志緩存區,相當於C++中流操作的flush或者C中文件操作的fflush。另外注意Flush實現里有如下代碼:
//......
{
MutexLockl(&log_mutex);
(this->*(data_->send_method_))();
++num_messages_[static_cast<int>(data_->severity_)];
}
//......
這是為了保證多個日志同時向同一介質進行輸出時到保持有序。注意鎖的使用前后有{}包圍。呵呵,這種用法其實我也偶爾使用,好處就是在一個比較大的 語句塊中創建一個作用域更小的對象,這樣能使該對象及早釋放,避免和整個語句塊使用同一作用域。比如上面代碼中的在加鎖時使用了一個更小的作用域,該作用 域結束后鎖就會立刻釋放,而不是等到Flush函數返回時才釋放,這樣就進一步提高了響應時間。
LOG_IF(INFO,num_cookies > 10) << "Gotlots of cookies"; //當條件滿足時輸出日志
LOG_EVERY_N(INFO, 10) << "Gotthe " << google::COUNTER<< "thcookie"; //google::COUNTER記錄該語句被執行次數,從1開始,在第一次運行輸出日志之后,每隔 10 次再輸出一次日志信息
LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Gotthe " << google::COUNTER<< "th bigcookie"; //上述兩者的結合,不過要注意,是先每隔 10 次去判斷條件是否滿足,如果滯則輸出日志;而不是當滿足某條件的情況下,每隔 10 次輸出一次日志信息。
LOG_FIRST_N(INFO, 20) << "Gotthe " << google::COUNTER<< "thcookie"; //當此語句執行的前 20 次都輸出日志,然后不再輸出
演示代碼如下:
#include <glog/logging.h>
int main(int argc,char* argv[])
{
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold=google::INFO;
FLAGS_colorlogtostderr=true;
for(int i = 1; i <= 100;i++)
{
LOG_IF(INFO,i==100)<<"LOG_IF(INFO,i==100) google::COUNTER="<<google::COUNTER<<" i="<<i;
LOG_EVERY_N(INFO,10)<<"LOG_EVERY_N(INFO,10) google::COUNTER="<<google::COUNTER<<" i="<<i;
LOG_IF_EVERY_N(WARNING,(i>50),10)<<"LOG_IF_EVERY_N(INFO,(i>50),10) google::COUNTER="<<google::COUNTER<<" i="<<i;
LOG_FIRST_N(ERROR,5)<<"LOG_FIRST_N(INFO,5) google::COUNTER="<<google::COUNTER<<" i="<<i;
}
google::ShutdownGoogleLogging();
}
LOG //內置日志
VLOG //自定義日志
DLOG //DEBUG模式可輸出的日志
DVLOG //DEBUG模式可輸出的自定義日志
SYSLOG //系統日志,同時通過 syslog() 函數寫入到/var/log/message 文件
PLOG //perror風格日志,設置errno狀態並輸出到日志中
RAW_LOG //線程安全的日志,需要#include <glog/raw_logging.h>
前六種的日志使用方法完全相同(包括條件日志輸出),而RAW_LOG 使用方法比較特殊,且不支持條件日志輸出,另外不接受colorlogtostderr 的顏色設置。自定義日志也不接受 colorlogtostderr的顏色設置,另外其日志嚴重級別也為自定義數字,且與默認日志嚴重級別相反,數字越小 嚴重級別越高。如:
#include <glog/logging.h>
#include <glog/raw_logging.h>
class GLogHelper
{
public:
GLogHelper(char*program)
{
google::InitGoogleLogging(program);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
FLAGS_v = 3;
}
~GLogHelper()
{
google::ShutdownGoogleLogging();
}
};
int main(int argc,char* argv[])
{
GLogHelpergh(argv[0]);
LOG(ERROR) <<"LOG";
VLOG(3) <<"VLOG";
DLOG(ERROR) <<"DLOG";
DVLOG(3) <<"DVLOG";
SYSLOG(ERROR) <<"SYSLOG";
PLOG(ERROR) <<"PLOG";
RAW_LOG(ERROR,"RAW_LOG");
}
當通過該宏指定的條件不成立的時候,程序會中止,並且記錄對應的日志信息。功能類似於ASSERT,區別是 CHECK 宏不受 NDEBUG 約束,在 release 版中同樣有效。
我個人感覺這類CHECK_XX宏比上面的LOG宏實現的還要隱晦難懂,當然設計的還是很巧妙的,值得學習一下,我嘗試做個分析。
在測試工程的logging_unittest.cc文件line535的TestCHECK()函數中有如下代碼:
CHECK_NE(1, 2);
CHECK_GE(1, 1);
CHECK_GE(2, 1);
CHECK_LE(1, 1);
CHECK_LE(1, 2);
定義如下:
#define CHECK_EQ(val1, val2) CHECK_OP(_EQ, ==,val1, val2)
#define CHECK_NE(val1, val2) CHECK_OP(_NE, !=,val1, val2)
#define CHECK_LE(val1, val2) CHECK_OP(_LE, <=,val1, val2)
#define CHECK_LT(val1, val2) CHECK_OP(_LT, < ,val1, val2)
#define CHECK_GE(val1, val2) CHECK_OP(_GE, >=,val1, val2)
#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宏定義如下:
typedef std::string_Check_string;
#define CHECK_OP_LOG(name, op, val1, val2,log) \
while(google::_Check_string* _result = \
google::Check##name##Impl( \
google::GetReferenceableValue(val1), \
google::GetReferenceableValue(val2), \
#val1 " " #op " " #val2)) \
log(__FILE__, __LINE__, \
google::CheckOpString(_result)).stream()
接下來我們以CHECK_LE(1,2);為例,將其逐步擴展:
CHECK_LE(1, 2)
------>CHECK_OP(_LE, <=, 1, 2)
------>CHECK_OP_LOG(_LE, <=, 1, 2,google::LogMessageFatal)
------>#define CHECK_OP_LOG(_LE, <=, 1, 2,google::LogMessageFatal) \
while (std::string* _result = \
google::Check_LEImpl( \
1, \
2, \
"1<= 2")) \
log(__FILE__,__LINE__, \
google::CheckOpString(_result)).stream()
其中google::Check_LEImpl也是通過宏預先實現的,這個宏就是DEFINE_CHECK_OP_IMPL(Check_LE,<=):
#define DEFINE_CHECK_OP_IMPL(name, op) \
template<typename T1, typename T2> \
inlinestd::string* name##Impl(const T1& v1, const T2& v2, \
const char*exprtext) { \
if(GOOGLE_PREDICT_TRUE(v1 op v2)) return NULL; \
elsereturn MakeCheckOpString(v1, v2, exprtext); \
} \
inlinestd::string* name##Impl(int v1, int v2, const char* exprtext) { \
returnname##Impl<int, int>(v1, v2, exprtext); \
}
展開后就實現了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,則不再執行下面的輸出,否則則輸出日志信息。
至此,就完成了CHECK_LE(1,2)的擴展,如果檢測為true,則返回NULL,否則就會返回一個有明確提示信息的字符串指針,並輸出該信息,然后是程序宕掉。
通過 google::InstallFailureSignalHandler();即可注冊,將 coredumped 信息輸出到 stderr,如:
#include <glog/logging.h>
#include <string>
#include <fstream>
//將信息輸出到單獨的文件和LOG(ERROR)
void SignalHandle(const char* data, int size)
{
std::ofstreamfs("glog_dump.log",std::ios::app);
std::stringstr = std::string(data,size);
fs<<str;
fs.close();
LOG(ERROR)<<str;
}
class GLogHelper
{
public:
GLogHelper(char*program)
{
google::InitGoogleLogging(program);
FLAGS_colorlogtostderr=true;
google::InstallFailureSignalHandler();
//默認捕捉 SIGSEGV 信號信息輸出會輸出到stderr,可以通過下面的方法自定義輸出方式:
google::InstallFailureWriter(&SignalHandle);
}
~GLogHelper()
{
google::ShutdownGoogleLogging();
}
};
void fun()
{
int* pi = new int;
delete pi;
pi = 0;
int j = *pi;
}
int main(int argc,char* argv[])
{
GLogHelpergh(argv[0]);
fun();
}
如果不使用 google::InstallFailureSignalHandler(); 則只會輸出“段錯誤” 三個字,難於排查。
google::SetLogDestination(google::ERROR,"log/prefix_"); //第一個參數為日志級別,第二個參數表示輸出目錄及日志文件名前綴。
google::SetStderrLogging(google::INFO); //輸出到標准輸出的時候大於 INFO級別的都輸出;等同於 FLAGS_stderrthreshold=google::INFO;
FLAGS_logbufsecs =0; //實時輸出日志
FLAGS_max_log_size =100; //最大日志大小(MB)
#define GOOGLE_STRIP_LOG 3 // 小於此級別的日志語句將在編譯時清除,以減小編譯后的文件大小,必須放在#include 前面才有效。
如果可執行文件名為"test",則將日志輸出到文件后,還會生成 test.ERROR,test.WARNING,test.INFO三個鏈接文件,分別鏈接到對應級別的日志文件。如果日志輸出超過 FLAGS_max_log_size 設置的大小,則會分為多個文件存儲,鏈接文件就會指向其中最新的對應級別的日志文件。所以當日志文件較多時,查看鏈接文件來查看最新日志挺方便的。
glog默認是根據進程ID是否改變和文件大小是否超過預定值來確定是否需要新建日志文件的,此處可以參考glog源碼logging.cc 文件中的 voidLogFileObject::Write 函數中
if (static_cast<int>(file_length_>> 20) >=MaxLogSize() ||
PidHasChanged()) {
我們只需要在此處加一個日期判斷就可以了,PidHasChanged()定義於utilities.cc 文件中,可以加一個類似的DayHasChanged() 函數(注意 utilities.h文件中加上函數聲明):
static int32 g_main_day = 0;
bool DayHasChanged()
{
time_traw_time;
struct tm*tm_info;
time(&raw_time);
tm_info =localtime(&raw_time);
if (tm_info->tm_mday!= g_main_day)
{
g_main_day = tm_info->tm_mday;
return true;
}
return false;
}
再修改 voidLogFileObject::Write 函數中的判斷條件即可:
if (static_cast<int>(file_length_>> 20) >=MaxLogSize() ||
PidHasChanged() ||DayHasChanged()) {