Glog是什么意思,Glog日志

 2023-12-25 阅读 34 评论 0

摘要:版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/breaksoftware/article/details/51363353 </div><link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_view
				版權聲明:本文為博主原創文章,未經博主允許不得轉載。					https://blog.csdn.net/breaksoftware/article/details/51363353				</div><link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-f57960eb32.css"><link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-f57960eb32.css"><div class="htmledit_views" id="content_views"><p>&nbsp; &nbsp; &nbsp; &nbsp; GLog是Google開發的一套日志輸出框架。由于其具有功能強大、方便使用等特性,它被眾多開源項目使用。本文將通過分析其源碼,解析Glog實現的過程。</p>

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

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

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

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. google::InitGoogleLogging(argv[0]);
  4. FLAGS_log_dir = "D:\\Dev\\glog-0.3.3\\glog-0.3.3\\Debug\\log";
  5. LOG(INFO) << "INFO";
  6. LOG(INFO) << "INFO1";
  7. LOG(WARNING) << "WARNING";
  8. LOG(WARNING) << "WARNING1";
  9. LOG(ERROR) << "ERROR";
  10. LOG(ERROR) << "ERROR1";
  11. LOG(FATAL) << "FATAL";
  12. google::ShutdownGoogleLogging();
  13. return 0;
  14. }

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

  1. static LogDestination* log_destinations_[NUM_SEVERITIES];
  2. void LogDestination::DeleteLogDestinations() {
  3. for (int severity = 0; severity < NUM_SEVERITIES; ++severity) {
  4. delete log_destinations_[severity];
  5. log_destinations_[severity] = NULL;
  6. }
  7. }

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

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

? ? ? ? 05到11行向GLog系統中輸出了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是設備的ID,username是登錄用戶的名稱。

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

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

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

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

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

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

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

? ? ? ? 通過對不同日志類型的LogMessage構造對象對比說明,它們是通過LogMessage的第三個參數進行區分的,上例中ERROR日志的等級就是google::GLOG_ERROR。在log_severity.h文件中,定義了GLog四個等級對應的值。

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

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

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

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

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

  1. #if GOOGLE_STRIP_LOG == 0
  2. #define COMPACT_GOOGLE_LOG_INFO google::LogMessage( \
  3. __FILE__, __LINE__)
  4. #define LOG_TO_STRING_INFO(message) google::LogMessage( \
  5. __FILE__, __LINE__, google::GLOG_INFO, message)
  6. #else
  7. #define COMPACT_GOOGLE_LOG_INFO google::NullStream()
  8. #define LOG_TO_STRING_INFO(message) google::NullStream()
  9. #endif
  10. #if GOOGLE_STRIP_LOG <= 1
  11. #define COMPACT_GOOGLE_LOG_WARNING google::LogMessage( \
  12. __FILE__, __LINE__, google::GLOG_WARNING)
  13. #define LOG_TO_STRING_WARNING(message) google::LogMessage( \
  14. __FILE__, __LINE__, google::GLOG_WARNING, message)
  15. #else
  16. #define COMPACT_GOOGLE_LOG_WARNING google::NullStream()
  17. #define LOG_TO_STRING_WARNING(message) google::NullStream()
  18. #endif
  19. #if GOOGLE_STRIP_LOG <= 2
  20. #define COMPACT_GOOGLE_LOG_ERROR google::LogMessage( \
  21. __FILE__, __LINE__, google::GLOG_ERROR)
  22. #define LOG_TO_STRING_ERROR(message) google::LogMessage( \
  23. __FILE__, __LINE__, google::GLOG_ERROR, message)
  24. #else
  25. #define COMPACT_GOOGLE_LOG_ERROR google::NullStream()
  26. #define LOG_TO_STRING_ERROR(message) google::NullStream()
  27. #endif
  28. #if GOOGLE_STRIP_LOG <= 3
  29. #define COMPACT_GOOGLE_LOG_FATAL google::LogMessageFatal( \
  30. __FILE__, __LINE__)
  31. #define LOG_TO_STRING_FATAL(message) google::LogMessage( \
  32. __FILE__, __LINE__, google::GLOG_FATAL, message)
  33. #else
  34. #define COMPACT_GOOGLE_LOG_FATAL google::NullStreamFatal()
  35. #define LOG_TO_STRING_FATAL(message) google::NullStreamFatal()
  36. #endif

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

? ? ? ? 以一個常用的LogMessage的構造函數來看臨時對象的初始化

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

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

  1. // log this message to all log files of severity <= severity_
  2. LogDestination::LogToAllLogfiles(data_->severity_, data_->timestamp_,
  3. data_->message_text_,
  4. data_->num_chars_to_log_);
  5. LogDestination::MaybeLogToStderr(data_->severity_, data_->message_text_,
  6. data_->num_chars_to_log_);
  7. LogDestination::MaybeLogToEmail(data_->severity_, data_->message_text_,
  8. data_->num_chars_to_log_);
  9. LogDestination::LogToSinks(data_->severity_,
  10. data_->fullname_, data_->basename_,
  11. data_->line_, &data_->tm_time_,
  12. data_->message_text_ + data_->num_prefix_chars_,
  13. (data_->num_chars_to_log_
  14. - data_->num_prefix_chars_ - 1));

? ? ? ? 這段代碼分別是:嘗試向文件中輸出、嘗試向標準錯誤流中輸出、嘗試發送郵件、嘗試向用戶自定義池中輸出。

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

?? ? ? ?LogToAllLogfiles函數不僅具有向文件輸出的能力,還有向標準錯誤流中輸出的能力。

  1. inline void LogDestination::LogToAllLogfiles(LogSeverity severity,
  2. time_t timestamp,
  3. const char* message,
  4. size_t len) {
  5. if ( FLAGS_logtostderr ) { // global flag: never log to file
  6. ColoredWriteToStderr(severity, message, len);
  7. } else {
  8. for (int i = severity; i >= 0; --i)
  9. LogDestination::MaybeLogToLogfile(i, timestamp, message, len);
  10. }
  11. }

? ? ? ? else中的for循環就是之前我們講解的日志包含的實現。

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

  1. inline void LogDestination::MaybeLogToLogfile(LogSeverity severity,
  2. time_t timestamp,
  3. const char* message,
  4. size_t len) {
  5. const bool should_flush = severity > FLAGS_logbuflevel;
  6. LogDestination* destination = log_destination(severity);
  7. destination->logger_->Write(should_flush, timestamp, message, len);
  8. }

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

  1. LogDestination* LogDestination::log_destinations_[NUM_SEVERITIES];
  2. inline LogDestination* LogDestination::log_destination(LogSeverity severity) {
  3. assert(severity >=0 && severity < NUM_SEVERITIES);
  4. if (!log_destinations_[severity]) {
  5. log_destinations_[severity] = new LogDestination(severity, NULL);
  6. }
  7. return log_destinations_[severity];
  8. }

? ? ? ? 如此可見,我們的測試代碼將新建四個LogDestination對象,并將指針保存在全局數組log_destinations_中。這四個指針分別對應于INFO、WARNING、ERROR和FATAL日志的輸出目標對象。所以,不管在多線程還是在單線程環境中,我們日志輸出都將調用到這四個對象指針。
? ? ? ?LogDestination類有個用于實際輸出日志的成員變量

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

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

  1. LogDestination::LogDestination(LogSeverity severity,
  2. const char* base_filename)
  3. : fileobject_(severity, base_filename),
  4. logger_(&fileobject_) {
  5. }

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

  1. LOG(ERROR) << "ERROR";
  2. LOG(ERROR) << "ERROR1";

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

  1. LogMessage::~LogMessage() {
  2. Flush();
  3. delete allocated_;
  4. }

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

  1. {
  2. MutexLock l(&log_mutex);
  3. (this->*(data_->send_method_))();
  4. ++num_messages_[static_cast<int>(data_->severity_)];
  5. }
  6. LogDestination::WaitForSinks(data_);

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

? ? ? ? 我們可以總結下,每條日志的輸出都通過一個LogMessage臨時對象的析構,傳遞到全局變量log_destinations_中相應等級對應的LogDestination指針所指向的對象。那么LogDestination對象又是在何時進行日志寫入文件的呢?我們知道文件的Write方法并不一定馬上把內容寫入文件,而是存在一定的緩存中,再根據系統的判斷或者用戶主動調用fflush,將數據實際寫入文件。LogFileObject提供了兩個Flush方法

  1. void LogFileObject::Flush() {
  2. MutexLock l(&lock_);
  3. FlushUnlocked();
  4. }
  5. void LogFileObject::FlushUnlocked(){
  6. if (file_ != NULL) {
  7. fflush(file_);
  8. bytes_since_flush_ = 0;
  9. }
  10. // Figure out when we are due for another flush.
  11. const int64 next = (FLAGS_logbufsecs
  12. * static_cast<int64>(1000000)); // in usec
  13. next_flush_time_ = CycleClock_Now() + UsecToCycles(next);
  14. }

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

  1. // See important msgs *now*. Also, flush logs at least every 10^6 chars,
  2. // or every "FLAGS_logbufsecs" seconds.
  3. if ( force_flush ||
  4. (bytes_since_flush_ >= 1000000) ||
  5. (CycleClock_Now() >= next_flush_time_) ) {
  6. FlushUnlocked();
  7. #ifdef OS_LINUX
  8. if (FLAGS_drop_log_memory) {
  9. if (file_length_ >= logging::kPageSize) {
  10. // don't evict the most recent page
  11. uint32 len = file_length_ & ~(logging::kPageSize - 1);
  12. posix_fadvise(fileno(file_), 0, len, POSIX_FADV_DONTNEED);
  13. }
  14. }
  15. #endif
  16. }

? ? ? ? 還有一種強制寫入文件的方法,就是LogDestination::FlushLogFilesUnsafe和LogDestination::FlushLogFiles方法,它們的實現是

  1. inline void LogDestination::FlushLogFilesUnsafe(int min_severity) {
  2. // assume we have the log_mutex or we simply don't care
  3. // about it
  4. for (int i = min_severity; i < NUM_SEVERITIES; i++) {
  5. LogDestination* log = log_destination(i);
  6. if (log != NULL) {
  7. // Flush the base fileobject_ logger directly instead of going
  8. // through any wrappers to reduce chance of deadlock.
  9. log->fileobject_.FlushUnlocked();
  10. }
  11. }
  12. }
  13. inline void LogDestination::FlushLogFiles(int min_severity) {
  14. // Prevent any subtle race conditions by wrapping a mutex lock around
  15. // all this stuff.
  16. MutexLock l(&log_mutex);
  17. for (int i = min_severity; i < NUM_SEVERITIES; i++) {
  18. LogDestination* log = log_destination(i);
  19. if (log != NULL) {
  20. log->logger_->Flush();
  21. }
  22. }
  23. }

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

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

  1. base::Logger* base::GetLogger(LogSeverity severity) {
  2. MutexLock l(&log_mutex);
  3. return LogDestination::log_destination(severity)->logger_;
  4. }
  5. void base::SetLogger(LogSeverity severity, base::Logger* logger) {
  6. MutexLock l(&log_mutex);
  7. LogDestination::log_destination(severity)->logger_ = logger;
  8. }

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

  1. struct MyLogger : public base::Logger {
  2. string data;
  3. virtual void Write(bool /* should_flush */,
  4. time_t /* timestamp */,
  5. const char* message,
  6. int length) {
  7. data.append(message, length);
  8. }
  9. virtual void Flush() { }
  10. virtual uint32 LogSize() { return data.length(); }
  11. };
  12. static void TestWrapper() {
  13. fprintf(stderr, "==== Test log wrapper\n");
  14. MyLogger my_logger;
  15. base::Logger* old_logger = base::GetLogger(GLOG_INFO);
  16. base::SetLogger(GLOG_INFO, &my_logger);
  17. LOG(INFO) << "Send to wrapped logger";
  18. FlushLogFiles(GLOG_INFO);
  19. base::SetLogger(GLOG_INFO, old_logger);
  20. CHECK(strstr(my_logger.data.c_str(), "Send to wrapped logger") != NULL);
  21. }

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

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

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

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

? ? ? ? 最終它會在SendToLog方法中的LogToSinks中被調用。

  1. inline void LogDestination::LogToSinks(LogSeverity severity,
  2. const char *full_filename,
  3. const char *base_filename,
  4. int line,
  5. const struct ::tm* tm_time,
  6. const char* message,
  7. size_t message_len) {
  8. ReaderMutexLock l(&sink_mutex_);
  9. if (sinks_) {
  10. for (int i = sinks_->size() - 1; i >= 0; i--) {
  11. (*sinks_)[i]->send(severity, full_filename, base_filename,
  12. line, tm_time, message, message_len);
  13. }
  14. }
  15. }

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

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

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

  1. #define LOG_TO_SINK(sink, severity) \
  2. google::LogMessage( \
  3. __FILE__, __LINE__, \
  4. google::GLOG_ ## severity, \
  5. static_cast<google::LogSink*>(sink), true).stream()
  6. #define LOG_TO_SINK_BUT_NOT_TO_LOGFILE(sink, severity) \
  7. google::LogMessage( \
  8. __FILE__, __LINE__, \
  9. google::GLOG_ ## severity, \
  10. static_cast<google::LogSink*>(sink), false).stream()

? ? ? ? 它調用的是5個參數版本的LogMessage構造函數

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

? ? ? ? 構造函數中true或false標識是否需要調用之前分析過的SendToLog()方法,還是只是調用SendToSink方法

  1. void LogMessage::SendToSink() EXCLUSIVE_LOCKS_REQUIRED(log_mutex) {
  2. if (data_->sink_ != NULL) {
  3. RAW_DCHECK(data_->num_chars_to_log_ > 0 &&
  4. data_->message_text_[data_->num_chars_to_log_-1] == '\n', "");
  5. data_->sink_->send(data_->severity_, data_->fullname_, data_->basename_,
  6. data_->line_, &data_->tm_time_,
  7. data_->message_text_ + data_->num_prefix_chars_,
  8. (data_->num_chars_to_log_ -
  9. data_->num_prefix_chars_ - 1));
  10. }
  11. }

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

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

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/196792.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息