C++日志操作開源函數庫之Google-glog


  今天想給我的C++項目找一個開源的日志類,用於記錄系統日志,結果浪費了半個下午的時間。從網上搜索相關資料,找到以下幾個備選方案:

1、log4cplus  下載地址:http://sourceforge.net/projects/log4cplus/files/log4cplus-stable/1.1.0

2、log4cxx  下載地址:http://logging.apache.org/log4cxx/download.html  (log4cxx 是由JAVA實現的 log4j 開源的,用C++實現的一個開源庫)

3、glog  下載地址:http://code.google.com/p/google-glog/downloads/list 或者 https://github.com/google/glog

4、log4cpp  下載地址: http://sourceforge.net/projects/log4cpp/files

  我初步看了一下,第1個和第4個是sourceforge網站上的開源項目,不知道是不是被屏蔽了,總之是打不開網頁。浪費我半小時找代理和翻牆,均找不到。看到第2個是Java注明的日志類log4j的姊妹C++版,頓時感覺應該不錯,結果編譯的過程讓我直接瘋掉了。先是下載log4cxx源代碼,又說光下載這個還不夠,還要下載另外兩個Apache項目(apr、apr-util)代碼,下完這三個還不行,在Windows下編譯還需要下一個Windows X64版本的Sed,好吧,這還不算數,居然還是鏈接失敗,網上還找不到相應的解決方案,整個過程浪費我4個小時。

  所以今天的教訓就是,人家的開源項目根本就不待見Windows平台,或者說根本不待見我這樣的菜鳥。既然我是Windows平台上的一只小菜鳥,那我就老老實實用簡單的東西吧。從第三方網站上下載Google glog,編譯一下居然輕松通過,很好!

一、基本用法測試

  1、從網上下載google-glog-v0.3.4-50-gde75983.zip,解壓到本地,可以看到工程文件google-glog.sln,可以用Visual Studio 2008打開。

  2、直接編譯就可以得到libglog.dll、libglog.lib等文件,這里的lib文件在編譯時要用,而dll文件則是運行時要用的,可以看出這里的glog采用的是“隱式”動態鏈接庫編譯方法。

  3、將解壓出來的文件夾路徑:“google-glog\src\windows”添加到Visual Studio 2008 的工程路徑中,以便於引用頭文件。同時將上一步編譯出來的lib文件加入路徑或者直接拷貝到自定義工程路徑中;

  4、使用如下代碼可以測試使用:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 #include <glog/logging.h>
 5 #pragma comment(lib, "libglog.lib")
 6 
 7 int main(int argc, char **argv)
 8 {
 9     google::InitGoogleLogging("KLH");
10     google::SetLogDestination(google::INFO, "F://");
11 
12     char str[20] = "hello log!";    
13     LOG(INFO) << "Found " << google::COUNTER <<endl;    
14     LOG(INFO) << str ;                // << " cookies";    
15     LOG(WARNING) << "warning test";    // 會輸出一個Warning日志    
16     LOG(ERROR) << "error test";        // 會輸出一個Error日志 
17 
18     system("pause");
19     return 0;
20 }

  5、編譯生成的exe文件還不能直接運行,需要將第3步編譯的dll文件拷貝到相同目錄中才能運行。測試的日志文件自動生成在F:/根目錄中。類似於如下格式內容:

1 Log file created at: 2015/12/14 20:28:18
2 Running on machine: VICTO-PC
3 Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
4 I1214 20:28:18.590252  9648 main2.cpp:13] Found 0
5 I1214 20:28:18.591253  9648 main2.cpp:14] hello log!
6 W1214 20:28:18.591253  9648 main2.cpp:15] warning test
7 E1214 20:28:18.592252  9648 main2.cpp:16] error test 

  6、其實這里可以看出,用動態鏈接庫編譯的方式有個麻煩的地方就是編譯出來的exe不能單獨運行,必須拷貝dll文件放到一起才行。如何采用靜態鏈接的方式呢?答案是要看官方doc文檔的說法:在自定義工程中添加“GOOGLE_GLOG_DLL_DECL=” 和 “GLOG_NO_ABBREVIATED_SEVERITIES” 這兩個宏,第二個宏主要是為了避免與windows.h沖突(下面會講到),第一個宏才是使用靜態鏈接庫時必須的!在編譯時可以編譯兩個版本:Release 和 Debug 以供調試時使用。

  7、在使用過程中一定要注意編譯參數 “/MT” 和 “/MD” 的區別,當然還有 “/MTd” 和 “/MDd” 的不同,可以免去復雜的鏈接錯誤、沖定義等問題,在下一篇日志中會專門講到,如果不清楚這個問題將會是很費勁的,包括之前寫過一篇libevent入門的文章說在使用時要忽略一大堆的系統函數庫,我覺得也是這個原因導致的。

、Windows平台應用

  當我在Windows平台項目上應用這個glog時遇到了一個問題,那就是直接編譯上面的代碼時會出現錯誤:

錯誤    2    fatal error C1189: #error :  ERROR macro is defined. Define GLOG_NO_ABBREVIATED_SEVERITIES before including logging.h. See the document for detail.    d:\cppworkspace\opensourcelibrary\google-glog\src\windows\glog\log_severity.h    55

  雙擊這個錯誤很容易定位到出錯的地方:

1 const int GLOG_INFO = 0, GLOG_WARNING = 1, GLOG_ERROR = 2, GLOG_FATAL = 3,
2   NUM_SEVERITIES = 4;
3 #ifndef GLOG_NO_ABBREVIATED_SEVERITIES
4 # ifdef ERROR
5 #  error ERROR macro is defined. Define GLOG_NO_ABBREVIATED_SEVERITIES before including logging.h. See the document for detail.
6 # endif
7 const int INFO = GLOG_INFO, WARNING = GLOG_WARNING,
8   ERROR = GLOG_ERROR, FATAL = GLOG_FATAL;
9 #endif

  其實解決這個錯誤倒是不難,可以看到只要是在Windows工程中預定義GLOG_NO_ABBREVIATED_SEVERITIES這個宏就可以了,但是為什么要這么做呢?找了一下,答案在官方文檔中:

Notes for Windows users
Google glog defines a severity level ERROR, which is also defined in windows.h . You can make glog not define INFO, WARNING, ERROR, and FATAL by defining GLOG_NO_ABBREVIATED_SEVERITIES before including glog/logging.h . Even with this macro, you can still use the iostream like logging facilities:
  #define GLOG_NO_ABBREVIATED_SEVERITIES
  #include <windows.h>
  #include <glog/logging.h>
  // ...
  LOG(ERROR) << "This should work";
  LOG_IF(ERROR, x > y) << "This should be also OK";
However, you cannot use INFO, WARNING, ERROR, and FATAL anymore for functions defined in glog/logging.h .
  #define GLOG_NO_ABBREVIATED_SEVERITIES
  #include <windows.h>
  #include <glog/logging.h>
  // ...
  // This won't work.
  // google::FlushLogFiles(google::ERROR);

  // Use this instead.
 google::FlushLogFiles(google::GLOG_ERROR);
If you don't need ERROR defined by windows.h, there are a couple of more workarounds which sometimes don't work:
#define WIN32_LEAN_AND_MEAN or NOGDI before you #include windows.h .
#undef ERROR after you #include windows.h .
See this issue for more detail.

   這下就清楚了,原來是glog中原來定義的ERROR宏與windows.h中的ERROR沖突了,為了避免沖突,glog作出了改變,增加了GLOG_NO_ABBREVIATED_SEVERITIES宏定義用以區分,那么在代碼中用google::GLOG_ERROR來代替原來的google::ERROR

、自定義glog日志格式

  要自定義glog輸出格式並不難,可以直接修改logging.cc文件中的LogMessage::Init函數,直接給出修改的位置:

 1 void LogMessage::Init(const char* file,
 2                       int line,
 3                       LogSeverity severity,
 4                       void (LogMessage::*send_method)()) {
 5 ... ...
 6   if (FLAGS_log_prefix && (line != kNoLogPrefix)) {
 7       /*
 8     stream() << LogSeverityNames[severity][0]
 9              << setw(2) << 1+data_->tm_time_.tm_mon
10              << setw(2) << data_->tm_time_.tm_mday
11              << ' '
12              << setw(2) << data_->tm_time_.tm_hour  << ':'
13              << setw(2) << data_->tm_time_.tm_min   << ':'
14              << setw(2) << data_->tm_time_.tm_sec   << "."
15              << setw(6) << usecs
16              << ' '
17              << setfill(' ') << setw(5)
18              << static_cast<unsigned int>(GetTID()) << setfill('0')
19              << ' '
20              << data_->basename_ << ':' << data_->line_ << "] ";
21              */
22       // Change output format. By KuLiuheng.    2015.12.15
23       stream()    << LogSeverityNames[severity][0]
24                 << ' '
25                 << setw(4) << 1900+data_->tm_time_.tm_year    << '-'
26                 << setw(2) << 1+data_->tm_time_.tm_mon    << '-'
27                 << setw(2) << data_->tm_time_.tm_mday
28                 << ' '
29                 << setw(2) << data_->tm_time_.tm_hour  << ':'
30                 << setw(2) << data_->tm_time_.tm_min   << ':'
31                 << setw(2) << data_->tm_time_.tm_sec   << "."
32                 << setw(6) << usecs
33                 << ' '
34                 << setfill(' ') << setw(5)
35                 << static_cast<unsigned int>(GetTID()) << setfill('0')
36                 << ' '
37                 << data_->basename_ << ':' << data_->line_ << "] ";
38   }
39 ... ...
40 }

   這樣一來輸出的格式就是:

Log file created at: 2015/12/15 14:31:05
Running on machine: VICTO-PC
Log line format: [IWEF] yyyy-mm-dd hh:mm:ss.uuuuuu threadid file:line] msg
I 2015-12-15 14:31:05.341013  2928 main2.cpp:14] Found 0
I 2015-12-15 14:31:05.343014  2928 main2.cpp:15] hello log!
W 2015-12-15 14:31:05.343014  2928 main2.cpp:16] warning test
E 2015-12-15 14:31:05.344014  2928 main2.cpp:17] error test

四、使用glog嚴重錯誤DUMP功能

  使用google::InstallFailureSignalHandler(); 和 google::InstallFailureWriter(&FatalMessageDump); 可以在程序出現嚴重錯誤時將詳細的錯誤信息打印出來,但是使用默認編譯的glog將會出現找不到此函數定義的問題,類似於:

error LNK2019: 無法解析的外部符號 "__declspec(dllimport) void __cdecl google::InstallFailureWriter(void (__cdecl*)(char const *,int))" (__imp_?InstallFailureWriter@google@@YAXP6AXPBDH@Z@Z),該符號在函數 "public: void __thiscall EnvironmentManager::Initialize(int,char * *,int)" (?Initialize@EnvironmentManager@@QAEXHPAPADH@Z) 中被引用
error LNK2019: 無法解析的外部符號 "__declspec(dllimport) void __cdecl google::InstallFailureSignalHandler(void)" (__imp_?InstallFailureSignalHandler@google@@YAXXZ),該符號在函數 "public: void __thiscall EnvironmentManager::Initialize(int,char * *,int)" (?Initialize@EnvironmentManager@@QAEXHPAPADH@Z) 中被引用

  這個時候只需要在默認的glog工程中,將signalhandler.cc 納入到libglog工程中,重新編譯即可。重新初始化glog的方法如下:

 1 // Start google log system:
 2 google::InitGoogleLogging(argv[0]);
 3 google::SetLogDestination(google::GLOG_INFO, ".//");
 4 google::SetStderrLogging(google::GLOG_INFO);
 5 google::SetLogFilenameExtension("log_");
 6 google::InstallFailureSignalHandler();
 7 google::InstallFailureWriter(&FatalMessageDump);
 8 FLAGS_colorlogtostderr = true;        // Set log color
 9 FLAGS_logbufsecs = 0;                // Set log output speed(s)
10 FLAGS_max_log_size = 1024;            // Set max log file size(GB)
11 FLAGS_stop_logging_if_full_disk = true;    // If disk is full

五、Linux/Uinux平台應用

  下載安裝包之后,輸入解壓命令,直接進入解壓后的目錄准備編譯:

./configue
make
make install

  特別提示一下,直接解壓后的configure 以及相關.sh文件有可能是沒有可執行權限的,用chmod a+x 給一下權限。如果要指定生成的文件路徑,那么就在配置的時候輸入:./configue --prefix=your_dir

  假設你的glog庫的路徑為/usr/local/lib/libglog.a,頭文件路徑為/usr/local/include/glog/logging.h,那么編譯命令如下:

g++ test.cpp -o test -L/usr/local/lib -lglog -I/usr/local/include/glog

  glog可以采用命令行的模式配置參數,這也是它靈活易用的體現,有兩種指定參數的方法,一種依賴於gflag如下:

./your_application --logtostderr=1     // 或者下面這種:
GLOG_logtostderr=1 ./your_application

  所有的環境變量均以GLOG_開頭,我們推薦使用第二種,一來不必依賴於gflag,二來當參數很多時,可以寫成腳本的形式,看起來更直觀,GLOG支持的flag如下(只列出常用的,如果想看全部的,可以在源碼的logging.cc文件下看到):

GLOG_logtostderr
  bool,默認為FALSE,將日志打印到標准錯誤,而不是日志文件
GLOG_alsologtostderr
  bool,默認為FALSE,將日志打印到日志文件,同時也打印到標准錯誤
GLOG_stderrthreshold
  int,默認為2(ERROR),大於等於這個級別的日志才打印到標准錯誤,當指定這個參數時,GLOG_alsologtostderr參數將會失效
GLOG_minloglevel
  int,默認為0(INFO), 小於這個日志級別的將不會打印
GLOG_log_dir
  string類型,指定日志輸出目錄,目錄必須存在
GLOG_max_log_size
  int,指定日志文件最大size,超過會被切割,單位為MB
GLOG_stop_logging_if_full_disk
  bool,默認為FALSE,當磁盤滿了之后不再打印日志
GLOG_v
  int,默認為0,指定GLOG_v=n時,對vlog(m),當m<=n時才會打印日志

  知道了這些參數之后,我們可以在腳本中指定這些變量,還是以test程序為例,test.sh如下:

#!/bin/sh
export GLOG_log_dir=log
export GLOG_minloglevel=1
export GLOG_stderrthreshold=1
export GLOG_v=3
export GLOG_max_log_size=1
./test

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM