日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

GoogleLog(GLog)源码分析

發(fā)布時間:2023/11/27 生活经验 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 GoogleLog(GLog)源码分析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

? ? ? ? GLog是Google開發(fā)的一套日志輸出框架。由于其具有功能強(qiáng)大、方便使用等特性,它被眾多開源項目使用。本文將通過分析其源碼,解析Glog實現(xiàn)的過程。

? ? ? ? 該框架的源碼在https://github.com/google/glog上可以獲取到。本文將以目前最新的0.3.3版本源碼為范例進(jìn)行分析。(轉(zhuǎn)載請指明出于breaksoftware的csdn博客)

? ? ? ? 首先我們介紹下Glog簡單的使用方法。Glog暴露了很多方法供使用者調(diào)用,一般它們都是通過宏的形式提供的。這些方法的功能包括:參數(shù)設(shè)置、判斷、日志輸出、自定義行為。基于重要性的考慮,我并不準(zhǔn)備在本文中介紹參數(shù)設(shè)置和判斷兩個特性的函數(shù)。而將重點放在Glog的核心——日志輸出和自定義行為上。而且在日志輸出環(huán)節(jié),我們將重點關(guān)注于文件形式的輸出。下文中很多未加設(shè)定的場景一般都是以文件輸出為例說明的。

? ? ? ? 我們以一個簡單的例子作為開始

int _tmain(int argc, _TCHAR* argv[])
{google::InitGoogleLogging(argv[0]);FLAGS_log_dir = "D:\\Dev\\glog-0.3.3\\glog-0.3.3\\Debug\\log";LOG(INFO) << "INFO";LOG(INFO) << "INFO1";LOG(WARNING) << "WARNING";LOG(WARNING) << "WARNING1";LOG(ERROR) << "ERROR";LOG(ERROR) << "ERROR1";LOG(FATAL) << "FATAL";google::ShutdownGoogleLogging();return 0;
}

? ? ? ? 第3和12行是對稱的,用于初始化和關(guān)閉Glog系統(tǒng)。這兩個過程的實現(xiàn)也非常簡單,其比較有意義的是ShutdownGoogleLogging中實現(xiàn)了對過程中新建對象的清理。即調(diào)用了

static LogDestination* log_destinations_[NUM_SEVERITIES];
void LogDestination::DeleteLogDestinations() {for (int severity = 0; severity < NUM_SEVERITIES; ++severity) {delete log_destinations_[severity];log_destinations_[severity] = NULL;}
}

? ? ? ??LogDestination是是GLog非常核心的一個類。按英文翻譯過來,它就是“日志目標(biāo)”類。正如它名稱,我們對日志的輸出最終將落到該類上去執(zhí)行。之后我們將一直和它打交道。

? ? ? ? 上面main函數(shù)的04行對Glog的全局變量重新賦值,它用于標(biāo)記日志文件的生成路徑。這類全局變量在logging.h中暴露了很多,它們很多是以DECLARE_bool、DECLARE_int32或DECLARE_string等宏聲明的。這些就是我前文所述的“參數(shù)”。我們先只要知道FLAGS_log_dir的作用就行了。

? ? ? ? 05到11行向GLog系統(tǒng)中輸出了4中類型的日志,即INFO、WARNING、ERROR和FATAL。GLog框架一共提供了上述四種類型的日志,這些日志將被分別輸出到四個文件中。如本例我們將生成如下四個文件:

glog_test.exe.computername.username.log.ERROR.20160510-153013.9704
glog_test.exe.computername.username.log.FATAL.20160510-153013.9704
glog_test.exe.computername.username.log.INFO.20160510-153013.9704
glog_test.exe.computername.username.log.WARNING.20160510-153013.9704

? ? ? ? 其中computername是設(shè)備的ID,username是登錄用戶的名稱。

? ? ? ? 這種設(shè)計是非常有意義的。在我們開發(fā)過程中,我們可以通過INFO類型的日志進(jìn)行過程分析。在自測階段,我們可能更多要關(guān)注于是否存在WARNING類型的日志。而上線后,可能會出現(xiàn)一些我們認(rèn)為不合法或者異常的場景,則這個時候就要關(guān)注ERROR和FATAL類型的日志了。這四種日志并不是相互獨立的,而是存在包含關(guān)系。按照重要性,INFO日志文件將包含INFO、WARNING、ERROR和FATAL日志,因為在開發(fā)過程中我們需要關(guān)注所有信息。WARNING日志文件將包含WARNING、ERROR和FATAL日志。ERROR日志將包含ERROR和FATAL日志。FATAL日志文件里只有FATAL類型的日志。這種層級關(guān)系將非常有助于我們在不同時期關(guān)注不同性質(zhì)的問題。我們看下INFO日志文件的內(nèi)容

Log file created at: 2016/05/10 15:30:13
Running on machine: COMPUTERNAME
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
I0510 15:30:13.564411 11392 glog_test.cpp:11] INFO
I0510 15:30:13.566411 11392 glog_test.cpp:12] INFO1
W0510 15:30:13.566411 11392 glog_test.cpp:13] WARNING
W0510 15:30:13.566411 11392 glog_test.cpp:14] WARNING1
E0510 15:30:13.567411 11392 glog_test.cpp:15] ERROR
E0510 15:30:13.567411 11392 glog_test.cpp:16] ERROR1
F0510 15:30:13.568413 11392 glog_test.cpp:17] FATAL

? ? ? ? 眼尖的同學(xué)可能發(fā)現(xiàn)INFO、ERROR和WARNING日志都有兩條,而FATAL日志只有一條。這個地方引出FATAL類型日志的使用場景問題。一般情況下,如果出現(xiàn)程序已經(jīng)無法執(zhí)行的場景才使用FATAL日志用于記錄臨死之前的事情。所以如果我們在項目中發(fā)現(xiàn)日志中出現(xiàn)一連串的FATAL日志,往往是對Glog的錯誤使用。
? ? ? ? Glog的基本使用我們講完了,我們開始進(jìn)行源碼的講解。

? ? ? ? 我們先看下LOG宏的定義

#define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()

? ? ? ? 可以見得,LOG宏的等級參數(shù)和COMPACT_GOOGLE_LOG_聯(lián)合在一塊組成一個宏。比如INFO則對應(yīng)于COMPACT_GOOGLE_LOG_INFO;FATAL對應(yīng)于COMPACT_GOOGLE_LOG_FATAL。這些宏最終都將對應(yīng)于一個google::LogMessage對象,比如

#define LOG_TO_STRING_ERROR(message) google::LogMessage( \__FILE__, __LINE__, google::GLOG_ERROR, message)

? ? ? ? 通過對不同日志類型的LogMessage構(gòu)造對象對比說明,它們是通過LogMessage的第三個參數(shù)進(jìn)行區(qū)分的,上例中ERROR日志的等級就是google::GLOG_ERROR。在log_severity.h文件中,定義了GLog四個等級對應(yīng)的值。

const int GLOG_INFO = 0, GLOG_WARNING = 1, GLOG_ERROR = 2, GLOG_FATAL = 3,NUM_SEVERITIES = 4;

? ? ? ? 這四個值并非隨便定義的。它們這樣設(shè)計,將有助于完成之前所說的日志包含輸出問題。在將日志輸出到文件的函數(shù)LogDestination::LogToAllLogfiles中

for (int i = severity; i >= 0; --i)LogDestination::MaybeLogToLogfile(i, timestamp, message, len);}

? ? ? ? 這段簡單的for循環(huán),讓日志輸出到自己以及低于自己等級的日志文件中。

? ? ? ? 在現(xiàn)實使用中,我們往往會通過一個臨時變量或者宏,來區(qū)分開發(fā)環(huán)境和線上環(huán)境。比如開發(fā)環(huán)境我們需要INFO級別的日志,而線上環(huán)境我們需要ERROR及其以上等級的日志。當(dāng)我們測試完畢,準(zhǔn)備上線前,我們不可能將輸出INFO類型日志的語句刪掉——因為可能很多、很分散且刪除代碼是不安全的。那么我們就需要使用一種手段讓INFO和WARNING等級的日志失效。GLog是這么做的

#if GOOGLE_STRIP_LOG == 0
#define COMPACT_GOOGLE_LOG_INFO google::LogMessage( \__FILE__, __LINE__)
#define LOG_TO_STRING_INFO(message) google::LogMessage( \__FILE__, __LINE__, google::GLOG_INFO, message)
#else
#define COMPACT_GOOGLE_LOG_INFO google::NullStream()
#define LOG_TO_STRING_INFO(message) google::NullStream()
#endif#if GOOGLE_STRIP_LOG <= 1
#define COMPACT_GOOGLE_LOG_WARNING google::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_WARNING google::NullStream()
#define LOG_TO_STRING_WARNING(message) google::NullStream()
#endif#if GOOGLE_STRIP_LOG <= 2
#define COMPACT_GOOGLE_LOG_ERROR google::LogMessage( \__FILE__, __LINE__, google::GLOG_ERROR)
#define LOG_TO_STRING_ERROR(message) google::LogMessage( \__FILE__, __LINE__, google::GLOG_ERROR, message)
#else
#define COMPACT_GOOGLE_LOG_ERROR google::NullStream()
#define LOG_TO_STRING_ERROR(message) google::NullStream()
#endif#if GOOGLE_STRIP_LOG <= 3
#define COMPACT_GOOGLE_LOG_FATAL google::LogMessageFatal( \__FILE__, __LINE__)
#define LOG_TO_STRING_FATAL(message) google::LogMessage( \__FILE__, __LINE__, google::GLOG_FATAL, message)
#else
#define COMPACT_GOOGLE_LOG_FATAL google::NullStreamFatal()
#define LOG_TO_STRING_FATAL(message) google::NullStreamFatal()
#endif

? ? ? ? 我們可以通過定義GOOGLE_STRIP_LOG的值,來控制相應(yīng)宏的定義。比如我們給GOOGLE_STRIP_LOG賦值2,則COMPACT_GOOGLE_LOG_WARNING會被定義為google::NullStream(),從而不會進(jìn)行輸出。ERROR和FATAL都將定義為LogMessage的臨時對象進(jìn)行之后的業(yè)務(wù)邏輯。

? ? ? ? 以一個常用的LogMessage的構(gòu)造函數(shù)來看臨時對象的初始化

LogMessage::LogMessage(const char* file, int line, LogSeverity severity): allocated_(NULL) {Init(file, line, severity, &LogMessage::SendToLog);
}

? ? ? ? 該函數(shù)中傳入了執(zhí)行語句所在的文件名和行號,這些我們都將在輸出的日志中見到過。第三個參數(shù)是日志等級,即GLOG_INFO等值。Init傳入的第四個參數(shù)是該類的一個成員函數(shù)地址。它才是我們關(guān)注的重點。
? ? ? ??LogMessage::SendToLog函數(shù)主要執(zhí)行的是下面這段代碼

    // log this message to all log files of severity <= severity_LogDestination::LogToAllLogfiles(data_->severity_, data_->timestamp_,data_->message_text_,data_->num_chars_to_log_);LogDestination::MaybeLogToStderr(data_->severity_, data_->message_text_,data_->num_chars_to_log_);LogDestination::MaybeLogToEmail(data_->severity_, data_->message_text_,data_->num_chars_to_log_);LogDestination::LogToSinks(data_->severity_,data_->fullname_, data_->basename_,data_->line_, &data_->tm_time_,data_->message_text_ + data_->num_prefix_chars_,(data_->num_chars_to_log_- data_->num_prefix_chars_ - 1));

? ? ? ? 這段代碼分別是:嘗試向文件中輸出、嘗試向標(biāo)準(zhǔn)錯誤流中輸出、嘗試發(fā)送郵件、嘗試向用戶自定義池中輸出。

? ? ? ? 嘗試發(fā)送郵件的方式我們很少使用到,它實際是借用了linux系統(tǒng)上/bin/mail程序去發(fā)送郵件,所以對這塊有興趣的同學(xué),可以主要關(guān)注下郵件內(nèi)容的組裝和發(fā)送命令的使用。

?? ? ? ?LogToAllLogfiles函數(shù)不僅具有向文件輸出的能力,還有向標(biāo)準(zhǔn)錯誤流中輸出的能力。

inline void LogDestination::LogToAllLogfiles(LogSeverity severity,time_t timestamp,const char* message,size_t len) {if ( FLAGS_logtostderr ) {           // global flag: never log to fileColoredWriteToStderr(severity, message, len);} else {for (int i = severity; i >= 0; --i)LogDestination::MaybeLogToLogfile(i, timestamp, message, len);}
}

? ? ? ? else中的for循環(huán)就是之前我們講解的日志包含的實現(xiàn)。

? ? ? ??MaybeLogToLogfile方法將讓一個全局的LogDestination對象執(zhí)行日志輸出邏輯

inline void LogDestination::MaybeLogToLogfile(LogSeverity severity,time_t timestamp,const char* message,size_t len) {const bool should_flush = severity > FLAGS_logbuflevel;LogDestination* destination = log_destination(severity);destination->logger_->Write(should_flush, timestamp, message, len);
}

? ? ? ??log_destination方法將通過日志等級新建并返回或者獲取一個保存在全局區(qū)域中LogDestination指針

LogDestination* LogDestination::log_destinations_[NUM_SEVERITIES];inline LogDestination* LogDestination::log_destination(LogSeverity severity) {assert(severity >=0 && severity < NUM_SEVERITIES);if (!log_destinations_[severity]) {log_destinations_[severity] = new LogDestination(severity, NULL);}return log_destinations_[severity];
}

? ? ? ? 如此可見,我們的測試代碼將新建四個LogDestination對象,并將指針保存在全局?jǐn)?shù)組log_destinations_中。這四個指針分別對應(yīng)于INFO、WARNING、ERROR和FATAL日志的輸出目標(biāo)對象。所以,不管在多線程還是在單線程環(huán)境中,我們?nèi)罩据敵龆紝⒄{(diào)用到這四個對象指針。
? ? ? ?LogDestination類有個用于實際輸出日志的成員變量

  LogFileObject fileobject_;base::Logger* logger_;      // Either &fileobject_, or wrapper around it

? ? ? ??Logger是個接口類,其暴露出來的方法都是純虛。所以實際操作的是其繼承類。LogFileObject繼承于Logger接口,對于文件類型的輸出,logger_指向fileobject_對象。

LogDestination::LogDestination(LogSeverity severity,const char* base_filename): fileobject_(severity, base_filename),logger_(&fileobject_) {
}

? ? ? ??LogFileObject的Write的實現(xiàn)非常簡單,除了在文件不存在的情況下新建了日志文件,還有就是日志個格式化輸出。它的整個邏輯是在鎖內(nèi)部完成的,這樣可以保證多線程寫操作是安全的。
? ? ? ? 我們分析完LogToAllLogfiles的實現(xiàn),再探索下它是在何處被調(diào)用的。之前我們講過LOG宏構(gòu)建了一個LogMessage臨時對象。這個臨時對象的生命周期就是C++語句中其所在的那一行的執(zhí)行周期。

    LOG(ERROR) << "ERROR";LOG(ERROR) << "ERROR1";

? ? ? ? 上述兩行,就構(gòu)建了兩個LogMessage臨時對象。第一行的臨時對象在第二行執(zhí)行之前就消亡了,第二行的臨時對象在之后一行執(zhí)行之前就消亡了。而對LogToAllLogfiles的調(diào)用就是在LogMessage的析構(gòu)函數(shù)中(實際是Flush中)。

LogMessage::~LogMessage() {Flush();delete allocated_;
}

? ? ? ? Flush中的核心代碼是

  {MutexLock l(&log_mutex);(this->*(data_->send_method_))();++num_messages_[static_cast<int>(data_->severity_)];}LogDestination::WaitForSinks(data_);

? ? ? ? 注意在調(diào)用send_method_所指向的函數(shù)(保存為文件時默認(rèn)的就是LogToAllLogfiles方法)之前上了鎖。這個鎖非常必要,可以保證之后的操作是受到保護(hù)的。否則之前在全局區(qū)域保存LogDestination對象指針的邏輯就存在多線程訪問的問題。

? ? ? ? 我們可以總結(jié)下,每條日志的輸出都通過一個LogMessage臨時對象的析構(gòu),傳遞到全局變量log_destinations_中相應(yīng)等級對應(yīng)的LogDestination指針?biāo)赶虻膶ο?。那么LogDestination對象又是在何時進(jìn)行日志寫入文件的呢?我們知道文件的Write方法并不一定馬上把內(nèi)容寫入文件,而是存在一定的緩存中,再根據(jù)系統(tǒng)的判斷或者用戶主動調(diào)用fflush,將數(shù)據(jù)實際寫入文件。LogFileObject提供了兩個Flush方法

void LogFileObject::Flush() {MutexLock l(&lock_);FlushUnlocked();
}void LogFileObject::FlushUnlocked(){if (file_ != NULL) {fflush(file_);bytes_since_flush_ = 0;}// Figure out when we are due for another flush.const int64 next = (FLAGS_logbufsecs* static_cast<int64>(1000000));  // in usecnext_flush_time_ = CycleClock_Now() + UsecToCycles(next);
}

? ? ? ? 一個是通過鎖保證多線程安全的版本,一個是不安全的版本。它們都是執(zhí)行了fflush,并計算了下一次flush時間。在LogFileObject的Write方法末尾,通過該時間的判斷,確定是否真的將緩存寫入文件中。

  // See important msgs *now*.  Also, flush logs at least every 10^6 chars,// or every "FLAGS_logbufsecs" seconds.if ( force_flush ||(bytes_since_flush_ >= 1000000) ||(CycleClock_Now() >= next_flush_time_) ) {FlushUnlocked();
#ifdef OS_LINUXif (FLAGS_drop_log_memory) {if (file_length_ >= logging::kPageSize) {// don't evict the most recent pageuint32 len = file_length_ & ~(logging::kPageSize - 1);posix_fadvise(fileno(file_), 0, len, POSIX_FADV_DONTNEED);}}
#endif}

? ? ? ? 還有一種強(qiáng)制寫入文件的方法,就是LogDestination::FlushLogFilesUnsafe和LogDestination::FlushLogFiles方法,它們的實現(xiàn)是

inline void LogDestination::FlushLogFilesUnsafe(int min_severity) {// assume we have the log_mutex or we simply don't care// about itfor (int i = min_severity; i < NUM_SEVERITIES; i++) {LogDestination* log = log_destination(i);if (log != NULL) {// Flush the base fileobject_ logger directly instead of going// through any wrappers to reduce chance of deadlock.log->fileobject_.FlushUnlocked();}}
}inline void LogDestination::FlushLogFiles(int min_severity) {// Prevent any subtle race conditions by wrapping a mutex lock around// all this stuff.MutexLock l(&log_mutex);for (int i = min_severity; i < NUM_SEVERITIES; i++) {LogDestination* log = log_destination(i);if (log != NULL) {log->logger_->Flush();}}
}

? ? ? ? 使用者可以通過這兩個函數(shù)人為主動的刷新緩沖區(qū),讓內(nèi)容落地。
? ? ? ? 以上,我們講解了GLog主要使用方法及其實現(xiàn)原理。實際GLog作為一個框架,也不失靈活性。

? ? ? ? 比如我們可以通過SetLogger方法修改不同等級的日志輸出方法。

base::Logger* base::GetLogger(LogSeverity severity) {MutexLock l(&log_mutex);return LogDestination::log_destination(severity)->logger_;
}void base::SetLogger(LogSeverity severity, base::Logger* logger) {MutexLock l(&log_mutex);LogDestination::log_destination(severity)->logger_ = logger;
}

? ? ? ? 下面代碼是其使用的一個實例

struct MyLogger : public base::Logger {string data;virtual void Write(bool /* should_flush */,time_t /* timestamp */,const char* message,int length) {data.append(message, length);}virtual void Flush() { }virtual uint32 LogSize() { return data.length(); }
};static void TestWrapper() {fprintf(stderr, "==== Test log wrapper\n");MyLogger my_logger;base::Logger* old_logger = base::GetLogger(GLOG_INFO);base::SetLogger(GLOG_INFO, &my_logger);LOG(INFO) << "Send to wrapped logger";FlushLogFiles(GLOG_INFO);base::SetLogger(GLOG_INFO, old_logger);CHECK(strstr(my_logger.data.c_str(), "Send to wrapped logger") != NULL);
}

? ? ? ? 我們還可以使用AddLogSink和RemoveLogSink方法自定義處理日志的邏輯。AddLogSink最終會調(diào)用到

inline void LogDestination::AddLogSink(LogSink *destination) {// Prevent any subtle race conditions by wrapping a mutex lock around// all this stuff.MutexLock l(&sink_mutex_);if (!sinks_)  sinks_ = new vector<LogSink*>;sinks_->push_back(destination);
}

? ? ? ? 其中sinks_是全局vector指針。它是獨立于之前介紹的log_destinations_數(shù)組管理的日志輸出方式。

  // arbitrary global logging destinations.static vector<LogSink*>* sinks_;

? ? ? ? 最終它會在SendToLog方法中的LogToSinks中被調(diào)用。

inline void LogDestination::LogToSinks(LogSeverity severity,const char *full_filename,const char *base_filename,int line,const struct ::tm* tm_time,const char* message,size_t message_len) {ReaderMutexLock l(&sink_mutex_);if (sinks_) {for (int i = sinks_->size() - 1; i >= 0; i--) {(*sinks_)[i]->send(severity, full_filename, base_filename,line, tm_time, message, message_len);}}
}

? ? ? ? 作為使用者,我們需要定義send方法,并根據(jù)日志等級的不同,處理不同的邏輯。下面是GLog測試代碼中的一個例子

  virtual void send(LogSeverity severity, const char* /* full_filename */,const char* base_filename, int line,const struct tm* tm_time,const char* message, size_t message_len) {// Push it to Writer thread if we are the original logging thread.// Note: Something like ThreadLocalLogSink is a better choice//       to do thread-specific LogSink logic for real.if (pthread_equal(tid_, pthread_self())) {writer_.Buffer(ToString(severity, base_filename, line,tm_time, message, message_len));}}

? ? ? ? 最后介紹LOG_TO_SINK和LOG_TO_SINK_BUT_NOT_TO_LOGFILE宏,它是設(shè)置單個Sink的方式(AddLogSink是設(shè)置一個到多個sink的方式)

#define LOG_TO_SINK(sink, severity) \google::LogMessage(                                    \__FILE__, __LINE__,                                               \google::GLOG_ ## severity,                         \static_cast<google::LogSink*>(sink), true).stream()
#define LOG_TO_SINK_BUT_NOT_TO_LOGFILE(sink, severity)                  \google::LogMessage(                                    \__FILE__, __LINE__,                                               \google::GLOG_ ## severity,                         \static_cast<google::LogSink*>(sink), false).stream()

? ? ? ? 它調(diào)用的是5個參數(shù)版本的LogMessage構(gòu)造函數(shù)

LogMessage::LogMessage(const char* file, int line, LogSeverity severity,LogSink* sink, bool also_send_to_log): allocated_(NULL) {Init(file, line, severity, also_send_to_log ? &LogMessage::SendToSinkAndLog :&LogMessage::SendToSink);data_->sink_ = sink;  // override Init()'s setting to NULL
}

? ? ? ? 構(gòu)造函數(shù)中true或false標(biāo)識是否需要調(diào)用之前分析過的SendToLog()方法,還是只是調(diào)用SendToSink方法

void LogMessage::SendToSink() EXCLUSIVE_LOCKS_REQUIRED(log_mutex) {if (data_->sink_ != NULL) {RAW_DCHECK(data_->num_chars_to_log_ > 0 &&data_->message_text_[data_->num_chars_to_log_-1] == '\n', "");data_->sink_->send(data_->severity_, data_->fullname_, data_->basename_,data_->line_, &data_->tm_time_,data_->message_text_ + data_->num_prefix_chars_,(data_->num_chars_to_log_ -data_->num_prefix_chars_ - 1));}
}

? ? ? ? 至此,我們便將Glog的實現(xiàn)原理分析完畢了。

? ? ? ? 在閱讀代碼和實驗其使用過程中,可以發(fā)現(xiàn)GLog是一個非常優(yōu)秀的日志開源庫。但是如果想讓GLog靈活的應(yīng)用于產(chǎn)品中,其實還有很多事情可以做。比如我們可以將參數(shù)放到配置文件中,這樣不至于我們每次修改參數(shù)時要重新編譯代碼。再比如我們可以定制自己的Sink,將日志數(shù)據(jù)發(fā)送到指定機(jī)器。

總結(jié)

以上是生活随笔為你收集整理的GoogleLog(GLog)源码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 黄视频网站在线看 | 国产欧美精品一区二区色综合 | 国产18av | 淫辱的世界(调教sm)by | 一区二区三区日韩欧美 | 精品人妻一区二区三区四区在线 | 9.1成人看片 | 国产亚洲成人av | 国产国语老龄妇女a片 | 国产黄在线观看 | 亚洲欧美偷拍视频 | 国产色黄 | 超碰国产在线观看 | 国外成人在线视频 | 青青草视频免费播放 | 韩日一区二区 | 国产又爽又黄又嫩又猛又粗 | 中文字字幕在线中文乱码 | 少妇人妻偷人精品无码视频新浪 | 日韩高清免费观看 | 黑人巨大精品人妻一区二区 | 骚虎视频最新网址 | 中文字幕免费观看视频 | 韩国伦理片在线播放 | 嫩草视频在线观看免费 | 日本成人在线一区 | 日韩污污 | 亚洲综合在 | 亲切的金子片段 | 灌篮高手全国大赛电影 | 亚洲最大的成人网站 | 中国人妖和人妖做爰 | 国产第2页 | 成人免费观看在线视频 | 欧美老肥婆性猛交视频 | 久久系列| 亚洲区中文字幕 | 91亚洲国产精品 | 亚洲日本精品视频 | 色偷偷五月天 | 亚洲色图欧美 | 91免费视频国产 | www.99av| 樱井莉亚av| 五月天亚洲综合 | 熟女高潮一区二区三区 | 秋霞欧美视频 | 艳母动漫在线播放 | 亚洲欧美国产日韩精品 | 国产精品久草 | 久久精品观看 | 骚视频在线观看 | 色呦呦视频在线 | 中文字字幕在线中文乱码 | 日本免费黄色网 | 日产久久久久久 | 91正在播放 | 一区二区三区国 | 一本大道伊人av久久综合 | a级国产视频 | 色哟哟免费视频 | 伊人网影院| 国产一区二区三区四区五区美女 | 日本成人精品在线 | 人人干人人爱 | 国产污网站 | 熟女人妻在线视频 | 欧美精品一区二区性色a+v | 欧美精品一区二区三区四区 | 深田咏美在线x99av | 免费a视频 | 国产成人不卡 | 成人在线视频一区二区三区 | 青青草视频免费观看 | 日本精品在线观看 | 性高跟丝袜xxxxhd | 无码精品黑人一区二区三区 | 高h奶汁双性受1v1 | 狠狠爱免费视频 | 性色浪潮av | 亚洲精品视频免费在线观看 | 亚av在线| 日本不卡一区二区 | 综合久久久 | 99热18 | 国产在线v | 毛片少妇 | 国产精品一区二区三区四区在线观看 | 一本视频在线 | 久久综合五月天 | 国产福利资源 | 日本视频久久 | ⅹxxxxhd亚洲日本hd老师 | 黄色一级一级 | 欧美一级二级三级视频 | 国产成人精品一区二区在线小狼 | 欧美丰满美乳xxⅹ高潮www | 香蕉视频在线观看免费 | 亚洲美女福利视频 |