項目中需要打log,當初看到glog,覺得google出品應該差不了,而且簡單易用,庫不是很大,就選擇他了。
但是在使用中還真的發現一些不順手和庫設計上的問題,反正和我的使用習慣有點不一樣。
- 設置log參數時有的用google::xx以函數的形式設置,有的以FLAG_xx的形式設置,而且有一些設置項兩種方式都可以,但是源碼中又走的不是相互封裝的關系,看着有點亂,沒理解glog為什么要這么設計,為什么不統一用一種方式。
- 在使用時我一直以為沒有調用google::InitGoogleLogging是不會打印任何log的,直到有用戶跟我反映說,他的程序里控制台一直會輸出log信息,我明明把輸出到控制台的選項都關閉了啊
FLAGS_logtostderr = 0; //是否打印到控制台 FLAGS_alsologtostderr = 0; //打印到日志同時是否打印到控制台
(這里也要吐槽下,不明白為什么要有FLAGS_alsologtostderr,輸出到控制台和輸出到日志文件分別有一個變量控制不行么,兩個變量控制同一個狀態真的好么)。后來看了glog源碼才發現google::InitGoogleLogging根本不控制什么東西啊,
void InitGoogleLogging(const char* argv0) { glog_internal_namespace_::InitGoogleLoggingUtilities(argv0); } void InitGoogleLoggingUtilities(const char* argv0) { CHECK(!IsGoogleLoggingInitialized()) << "You called InitGoogleLogging() twice!"; const char* slash = strrchr(argv0, '/'); #ifdef OS_WINDOWS if (!slash) slash = strrchr(argv0, '\\'); #endif g_program_invocation_short_name = slash ? slash + 1 : argv0; g_main_thread_id = pthread_self(); #ifdef HAVE_STACKTRACE InstallFailureFunction(&DumpStackTraceAndExit); #endif }
查了一下g_program_invocation_short_name,g_main_thread_id這兩個變量只是獲取一下信息為了防止重復初始化和打印時輸出線程ID,根本不控制是否可以輸出。不信再看下面
因為glog都是通過LOG(XXX)來打印log,所以找到源碼#define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream() ---------------------------------------------------- #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 ---------------------------------------------------- LogMessage::LogMessage(const char* file, int line, LogSeverity severity, int ctr, void (LogMessage::*send_method)()) : allocated_(NULL) { Init(file, line, severity, send_method); data_->stream_.set_ctr(ctr); } ---------------------------------------------------- void LogMessage::Init(const char* file, int line, LogSeverity severity, void (LogMessage::*send_method)()) { allocated_ = NULL; if (severity != GLOG_FATAL || !exit_on_dfatal) { allocated_ = new LogMessageData(); data_ = allocated_; data_->first_fatal_ = false; } else { MutexLock l(&fatal_msg_lock); if (fatal_msg_exclusive) { fatal_msg_exclusive = false; data_ = &fatal_msg_data_exclusive; data_->first_fatal_ = true; } else { data_ = &fatal_msg_data_shared; data_->first_fatal_ = false; } } stream().fill('0'); data_->preserved_errno_ = errno; data_->severity_ = severity; data_->line_ = 0; data_->send_method_ = send_method; data_->sink_ = NULL; data_->outvec_ = NULL; WallTime now = WallTime_Now(); data_->timestamp_ = static_cast<time_t>(now); localtime_r(&data_->timestamp_, &data_->tm_time_); int usecs = static_cast<int>((now - data_->timestamp_) * 1000000); RawLog__SetLastTime(data_->tm_time_, usecs); data_->num_chars_to_log_ = 0; data_->num_chars_to_syslog_ = 0; data_->basename_ = const_basename(""); data_->fullname_ = file; data_->has_been_flushed_ = false; // If specified, prepend a prefix to each line. For example: // I1018 160715 f5d4fbb0 logging.cc:1153] // (log level, GMT month, date, time, thread_id, file basename, line) // We exclude the thread_id for the default thread. if (FLAGS_log_prefix && (line != kNoLogPrefix)) { stream() << LogSeverityNames[severity][0] << setw(2) << 1+data_->tm_time_.tm_mon << setw(2) << data_->tm_time_.tm_mday << ' ' << setw(2) << data_->tm_time_.tm_hour << ':' << setw(2) << data_->tm_time_.tm_min << ':' << setw(2) << data_->tm_time_.tm_sec << "." << setw(6) << usecs << ' ' << setfill(' ') << setw(5) << static_cast<unsigned int>(GetTID()) << setfill('0') << ' ' << data_->basename_ << ':' << data_->line_ << "] "; } data_->num_prefix_chars_ = data_->stream_.pcount(); if (!FLAGS_log_backtrace_at.empty()) { char fileline[128]; snprintf(fileline, sizeof(fileline), "%s:%d", data_->basename_, line); #ifdef HAVE_STACKTRACE if (!strcmp(FLAGS_log_backtrace_at.c_str(), fileline)) { string stacktrace; DumpStackTraceToString(&stacktrace); stream() << " (stacktrace:\n" << stacktrace << ") "; } #endif } }
看到沒,其實都在LogMessage::Init中,執行了這么多代碼你有看到g_program_invocation_short_name,g_main_thread_id控制是否輸出么,也就是說就算你沒調用google::InitGoogleLogging,你執行LOG(XXX)<<XX;也會執行這么多代碼,在一個要求執行效率比較高的代碼里,這會浪費很多cpu時間啊。
到這我也明白了客戶那邊為什么會輸出控制台log,因為我以為沒調用google::InitGoogleLogging就不會執行任何glog函數,用戶使用我的接口時沒有調用google::InitGoogleLogging,但是接口里面直行到LOG(XXX)時還是會輸出到控制台信息。
但是還有一個問題,那就是為什么只輸出到控制台,沒有輸出到log文件,那么接着來看
隨便找一個函數看看 google::SetLogFilenameExtension(Extra_Name); ------------------------------------------------------- void SetLogFilenameExtension(const char* ext) { LogDestination::SetLogFilenameExtension(ext); } 可以知道google::xxx都是通過LogDestination類來設置的,但是LogDestination類在哪里初始化呢,找找new LogDestination吧 ------------------------------------------------------ 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]; } 可以看到,它的邏輯是使用severity等級來定義對象,每個等級對應一個LogDestination對象,有一個LogDestination的數組。其他函數也都一樣,每次都是從這個數組中取出對象來設置。 -------------------------------------------------------
因為我把log的初始化封裝到一個函數里,用戶沒有初始化,所以沒有調用任何google::xx,或者FLAGS_XX代碼,所以根據glog庫的默認值,是不會輸出到glog的,但是問題還是,我的接口中cpu還是會執行LOG(XXX),所以其實還是執行了很多glog庫里的代碼。
所以如果真的想不輸出log,不能通過glog函數來控制,需要自己再封裝一下,在自己的代碼中控制,這個glog實在太差勁了,這太坑。
這里也證明了,有人問FLAGS_XX,google::xx,應該在google::InitGoogleLogging之前還是之后執行,回答是沒關系,他倆根本不存在先后順序。
3. 用戶還提出一個需求,就是不希望log中顯示文件名和行號,我一開始以為這太容易了,肯定會有個設置項設置的,后來發現我想的太美了,哪有這個設置。還是老老實實看源碼吧
LogMessage::LogMessage(const char* file, int line, LogSeverity severity, int ctr, void (LogMessage::*send_method)()) : allocated_(NULL) { Init(file, line, severity, send_method); data_->stream_.set_ctr(ctr); }
前面我們知道了,輸出log其實歸根結底都是調用LogMessage,而且這兩個參數const char* file, int line在從對外的接口函數里一路傳下來,想不打印文件名和行號,只能自己改源碼再重新編譯庫dll了,因為LogMessage是必經之路,所以我就是在LogMessage改動,把file,line給個固定值,算是解決了問題。
可是如果每次有點問題就需要改源碼重新編譯庫文件,那也是夠夠的了,改來改去的,萬一將來有一天需要升級最新的glog版本,難道我要再拿最新的源碼把這些改動再改一邊么,想想就夠了。
所以啊,這第三方庫的選擇就跟去飯店吃飯一樣,冷清的飯店肯定是有問題的,要么菜不好吃,要么服務不行,要么有什么其他問題,還是隨大流比較好,小眾的東西還是謹慎嘗試,需要不拍踩坑才行。