
1. 為什么要在VS里引入glog日志庫選型的底層邏輯1.1 你真正需要的日志能力如果你在C項目里干過幾個月大概率會走到這一步printf打天下打到想吐調試信息散落各處線上崩潰日志全靠猜多線程環(huán)境下日志順序亂成一鍋粥。這時候就需要把日志這件事正經(jīng)地做成“基礎設施”而不是隨手寫幾行輸出。glog就是Google內部那套日志系統(tǒng)開源出來的版本C項目里用得非常多尤其在Windows Visual Studio以下簡稱VS這套環(huán)境下配置得當之后它的穩(wěn)定性和實用性非常能打。先說清楚glog到底幫你解決了什么。它不是你隨口調用的std::cout而是一套完整的日志框架核心能力包括分級日志輸出INFO / WARNING / ERROR / FATAL、按大小自動切割日志文件、每條日志自動帶時間戳和源碼位置、支持條件日志和每N條日志打一條這種細粒度控制、FATAL級別自動觸發(fā)程序終止并留下棧信息。這些能力覆蓋了絕大多數(shù)C項目對日志的真實需求從日常調試到線上問題追溯都用得上。我在真實項目里最看重的是三點。一是日志級別體系能讓INFO和ERROR各歸其位代碼評審時一眼就能看出哪里是正常流程、哪里是異常分支二是條件日志LOG_IF、LOG_EVERY_N這類宏能極大減少“日志刷屏”和“無效日志”的問題三是FATAL級別的崩潰鉤子配合調試器可以精確還原崩潰現(xiàn)場。所以這篇文章不打算只給一份“抄作業(yè)”式的配置步驟而是把“為什么這么配”“去哪編譯庫”“踩過哪些坑”一起講透你配置完心里有底后續(xù)擴展也順手。1.2 主流日志庫橫評glog、spdlog、log4cplus怎么選選glog之前建議你先花兩分鐘想清楚自己的場景因為C日志庫不止glog一個選錯方向后面會很痛苦。我日常用過的有spdlog、log4cplus、glog三套簡單對比一下對比維度glogspdloglog4cplus日志級別四級INFO/WARNING/ERROR/FATAL六級trace到critical多級可自定義性能側重中高重穩(wěn)定性極高異步模式強中功能全面配置方式命令行參數(shù) 環(huán)境變量代碼API配置文件上手難度低低中典型場景Google系項目、需要源碼級調試高頻日志、游戲服務器企業(yè)級老項目如果你的項目是高并發(fā)、日志量極大、對性能敏感到每秒幾萬條以上spdlog的異步模式會更強如果你是從Java log4j轉過來的老開發(fā)log4cplus的配置風格你會更熟悉。但如果你是在VS里做Windows桌面程序、工具鏈軟件、客戶端組件或者團隊本身是Google C風格glog是最穩(wěn)的選擇。為什么這么說glog對源碼位置的記錄、對FATAL語義的處理、對崩潰信號的捕獲在Windows上配合VS的調試體驗非常順。它不會給你整一堆用不上的配置項也沒有XML/JSON配置文件的額外學習成本編譯好之后把庫一鏈頭文件一引LOG(INFO)走天下。這篇博文全部以glog為主線展開。2. 獲取glog庫的兩種主流方式2.1 方式一vcpkg一鍵集成省心但要知道原理在VS里引入任何第三方C庫最大的痛點不是寫代碼而是“庫從哪來”。最省心的方案就是用vcpkg。vcpkg是微軟官方維護的C包管理器裝好之后一條命令就能把glog拉下來編譯好并且自動與VS工程集成。先看安裝vcpkg的標準流程。打開PowerShell或CMD找個干凈目錄執(zhí)行git clone https://github.com/microsoft/vcpkg.git cd vcpkg bootstrap-vcpkg.batbootstrap-vcpkg.bat會生成vcpkg.exe然后設置環(huán)境變量或者直接調用。接著安裝glogvcpkg install glog:x64-windows這里x64-windows指定的是64位動態(tài)庫版本。如果你用的是32位工程改成x86-windows如果你想用靜態(tài)庫用x64-windows-static。這一步會拉取glog依賴的gflags等庫一起編譯耗時取決于網(wǎng)絡和機器性能。安裝完最關鍵的一步是集成到VS。繼續(xù)執(zhí)行vcpkg integrate install這個命令會在VS里注冊一個全局配置讓你新建工程后不需要手動配置頭文件路徑和庫路徑就能直接#include glog/logging.h。聽著很美好不過我建議你仍然花兩分鐘看一下它到底做了什么它本質上是往VS的Microsoft.Cpp.x64.user屬性表里寫入了包含目錄和庫目錄。這意味著你打開任何一個VS工程的屬性頁都能在“VC目錄”里看到vcpkg的路徑。vcpkg方案適合快速搭環(huán)境、或者在多個工程之間共享同一套庫。但它有一個隱藏問題庫版本升級由vcpkg控制你沒法輕易“鎖定”某個commit如果團隊里有人更新了vcpkg到新版本glog的行為可能發(fā)生變化導致你的代碼突然編譯不過或者鏈接報錯。所以如果是正式項目建議在說明文檔里固定vcpkg的git版本或者直接走源碼編譯方案。2.2 方式二源碼編譯glog可控性最強的路線如果你不想依賴包管理器或者需要定制glog的編譯選項或者公司內網(wǎng)環(huán)境沒法隨意拉取vcpkg依賴那就老老實實從源碼編譯。這條路線其實不復雜我來拆解一遍。第一步獲取源碼git clone https://github.com/google/glog.git cd glog第二步用CMake生成VS工程。在glog目錄下新建一個build文件夾然后執(zhí)行cd build cmake .. -G Visual Studio 17 2022 -A x64 -DCMAKE_INSTALL_PREFIX./install這里的-G要跟你的VS版本對應VS2019就是Visual Studio 16 2019VS2022就是Visual Studio 17 2022。-A x64指定64位架構。CMAKE_INSTALL_PREFIX是安裝路徑我習慣放在build/install里方便后面引用。第三步編譯并安裝。直接打開生成的glog.sln用VS編譯也行我更推薦命令行方式干凈利落cmake --build . --config Release --target install注意--config ReleaseDebug版本可以單獨編一次但默認先出Release。編譯完成后你會發(fā)現(xiàn)build/install目錄下有include和lib兩個子目錄這就是后面配置VS工程所需要的全部東西。源碼編譯方案最大的優(yōu)勢是可控。你在CMake配置階段可以加一堆開關比如-DBUILD_SHARED_LIBSOFF編靜態(tài)庫、-DWITH_GFLAGSOFF去掉gflags依賴等。對于大多數(shù)不需要gflags的工程我建議直接關掉gflags減少依賴鏈。命令如下cmake .. -G Visual Studio 17 2022 -A x64 -DCMAKE_INSTALL_PREFIX./install -DBUILD_SHARED_LIBSOFF -DWITH_GFLAGSOFF這里面BUILD_SHARED_LIBSOFF會生成靜態(tài)庫WITH_GFLAGSOFF則讓glog不依賴gflags。關掉gflags之后你在代碼里就沒法用FLAGS_v這種gflags風格的命令行參數(shù)來控制日志了但對大部分場景沒有影響。3. VS工程配置的核心步驟3.1 工程屬性配置包含目錄、庫目錄、附加依賴項一次配齊拿到編譯好的glog無論是vcpkg還是源碼編譯下一步就是讓VS工程認這套庫。右鍵項目 - 屬性切到“所有配置”和“所有平臺”開始配置。不要只在Debug或Release里單獨配否則換個配置就編譯不過而且很難排查。先配“VC目錄”下的“包含目錄”把include路徑填進去。如果你用源碼編譯方案路徑就是build/install/include如果用vcpkg理論上不需要手動填但為了保險你還是可以檢查一下是否有vcpkg的路徑。然后是“庫目錄”把lib路徑填進去比如build/install/lib。最后是“鏈接器 - 輸入 - 附加依賴項”填庫文件名。這里有一個非常關鍵的細節(jié)Release版本和Debug版本的庫文件名不一樣。通常Release版是glog.libDebug版是glogd.lib。如果你只編譯了Release庫卻用Debug配置去鏈接會直接報LNK1104 cannot open file glogd.lib。解決方案有三種一是切換到“Debug”配置再填一次Debug的庫名二是把Debug和Release都編譯出來三是用宏區(qū)分在附加依賴項里寫glog.lib然后在“鏈接器 - 輸入 - 附加依賴項”旁邊有個“繼承的值”你可以在預處理宏里加一個_DEBUG分支效果一樣。但實際項目里我強烈建議Debug和Release的庫都裝上因為你總會在Debug下調試。3.2 運行庫匹配這是90%鏈接錯誤的根源配置完目錄和庫名你以為能編譯了先別急還有一個隱藏大坑運行庫不匹配。VS的C運行庫有兩種模式/MT靜態(tài)運行時和/MD動態(tài)運行時。如果你的工程用的是/MD默認值Release最常見但編譯glog時CMake設置成了靜態(tài)運行時鏈接時就會報一團亂麻的錯誤最常見的是LNK2038 mismatch detected for RuntimeLibrary: value MT_StaticRelease doesnt match value MD_DynamicRelease。務必要把工程屬性和glog編譯選項對齊。檢查方式在“C/C - 代碼生成 - 運行庫”里查看當前值。如果你用的是/MD那么源碼編譯glog時不要額外亂改CMake的運行庫參數(shù)直接用默認值CMake默認跟隨VS的設置如果你用靜態(tài)運行時/MT則需要在CMake時指定cmake .. -DCMAKE_CXX_FLAGS_RELEASE/MT -DCMAKE_CXX_FLAGS_DEBUG/MTd其實最穩(wěn)妥的辦法是先看自己工程的運行庫設置再據(jù)此確定glog的編譯選項兩邊一致再繼續(xù)。提示vcpkg的x64-windows默認編的是/MD動態(tài)庫如果你的工程是/MT需要安裝x64-windows-static版。這一點非常容易忽略很多人配完還是報LNK2038排查半天發(fā)現(xiàn)是運行庫不一致。4. 代碼接入與日志落地4.1 初始化glog與最常用的日志宏庫配置好之后開始寫代碼。先引入頭文件并初始化#include glog/logging.h int main(int argc, char* argv[]) { google::InitGoogleLogging(argv[0]); LOG(INFO) This is an info log; LOG(WARNING) This is a warning log; LOG(ERROR) This is an error log; google::ShutdownGoogleLogging(); return 0; }InitGoogleLogging接收程序名這個名稱會出現(xiàn)在日志文件名的前綴里。ShutdownGoogleLogging在程序退出前調用確保日志緩沖區(qū)全部落盤。寫到這里有幾個細節(jié)提醒。一是LOG(FATAL)會導致程序abort如果你不想讓它終止程序可以用LOG(ERROR)替代或者自定義google::InstallFailureFunction來接管FATAL行為。二是LOG_IF非常實用按條件打日志int ret DoSomething(); if (ret ! 0) { LOG(ERROR) DoSomething failed, ret ret; } // 等價寫法 LOG_IF(ERROR, ret ! 0) DoSomething failed, ret ret;三是LOG_EVERY_N控制頻次防止高頻循環(huán)里日志刷爆磁盤for (int i 0; i 100000; i) { LOG_EVERY_N(INFO, 1000) Processing i th item; }這個宏的意思是每1000次打一條日志對線上服務或者長跑分析程序來說非常有用。4.2 日志文件輸出與切割策略默認情況下glog是輸出到stderr的在VS里跑的時候“輸出窗口”可以看到但程序關閉后日志就沒了。要落盤有兩個辦法。方法一設置標志FLAGS_log_dir指定日志輸出目錄FLAGS_log_dir D:/logs/; google::InitGoogleLogging(argv[0]);這樣glog會自動在該目錄下生成文件命名規(guī)則是programname.hostname.user.log.severity.date.time.pid比如mytest.local.admin.log.ERROR.20241115-103025.12345。注意一旦指定了日志目錄INFO、WARNING、ERROR級別都會分別寫到對應文件里。說到目錄這個路徑必須真實存在glog不會自動創(chuàng)建目錄路徑不存在時日志會靜默丟失。我第一次用的時候就在這里栽過因為程序啟動時指定了一個不存在的目錄結果所有日志都不見了好不容易才排查出來。方法二使用glog的命令行參數(shù)機制。在InitGoogleLogging之后所有以FLAGS_開頭的變量都可以通過命令行參數(shù)覆蓋比如mytest.exe --log_dirD:/logs --minloglevel0這其實是gflags風格的繼承如果編譯時開了gflags生效--log_dir就能直接解析。如果關掉了gflagsFLAGS_log_dir仍然存在但命令行解析功能弱化建議直接用代碼設置。日志切割由glog內部自動處理默認單文件超過1GB左右會觸發(fā)切割——但說實話1GB對日常開發(fā)來說太大我一般用FLAGS_max_log_size調整單位是MBFLAGS_max_log_size 100; // 單文件100MB切割然后配合FLAGS_log_level、FLAGS_stderrthreshold來精細化控制。FLAGS_stderrthreshold表示某個級別及以上的日志同時輸出到stderr比如設成google::WARNING那么WARNING和ERROR日志除了寫文件還會打到控制臺方便開發(fā)時實時看。5. 編譯鏈接常見問題排查實錄5.1 經(jīng)典LNK錯誤與運行庫不匹配在VS里引第三方庫鏈接錯誤真是天天見我先把最常見的幾個羅列出來。LNK1104 cannot open file glog.lib庫目錄沒配對或者沒找到這個庫文件。先確認lib目錄下確實有glog.lib如果是Debug配置下的報錯多半是你沒編譯Debug庫而附加依賴項里填了glogd.lib但文件不存在。LNK2038 RuntimeLibrary mismatch運行庫不一致工程是/MD但glog是/MT編的或者反過來。解決辦法要么重新編glog要么改工程設置兩邊對齊。注意這個錯誤有時候不會直接報LNK2038而是變成一堆奇奇怪怪的外部符號錯誤比如__imp_??...無法解析別被表象迷惑先查運行庫。LNK2019 unresolved external symbol class std::basic_ostream...這個也經(jīng)常是運行庫不匹配導致的因為C標準庫的實現(xiàn)方式在/MT和/MD下不同。如果錯誤堆里混合了大量STL相關符號優(yōu)先檢查運行庫。LNK2001 unresolved external symbol void __cdecl google::InitGoogleLogging...這個更直白鏈接器找不到glog的函數(shù)實現(xiàn)。除了路徑配錯之外有一個隱蔽原因你可能沒定義GOOGLE_GLOG_DLL_DECL宏。當glog編譯成動態(tài)庫時頭文件里的導出導入聲明依賴這個宏缺少它會導致找到頭文件但找不到符號。解決方案是在預處理定義里加上GOOGLE_GLOG_DLL_DECL或者在源碼編譯glog時選擇靜態(tài)庫。5.2 DLL找不到與運行路徑問題如果你用的是動態(tài)庫版glog編譯鏈接都過了但程序一運行就報“找不到glog.dll”。這個問題的本質是動態(tài)庫沒有放到系統(tǒng)搜索路徑里。三個解法按優(yōu)先級排序一是把glog.dll復制到可執(zhí)行文件exe同目錄下最直接最穩(wěn)妥。二是把庫所在路徑加到系統(tǒng)環(huán)境變量PATH里但這個要重啟VS甚至重啟系統(tǒng)才生效容易造成環(huán)境污染不推薦。三是用VS的調試工作目錄設置把包含DLL的目錄設為“調試 - 工作目錄”。不過這個只在VS里調試時有效獨立運行exe還是會報錯。我建議直接用第一種構建后事件腳本自動拷貝copy /Y $(SolutionDir)..\lib\glog.dll $(TargetDir)如果用的是源碼編譯DLL通常都在build/install/bin下面拷到工程輸出目錄就行。注意如果同時用了多個第三方庫且它們依賴不同版本的同一個DLL那才是真正的噩夢。我遇到過glog和另一個庫都依賴不同版本的gflags導致運行時崩潰。這種問題的排查思路是用dumpbin /dependents your_exe.exe查看依賴鏈把所有DLL版本對齊。5.3 多線程環(huán)境與FATAL崩潰的真實體驗glog本身是線程安全的多線程并發(fā)打日志不會出現(xiàn)數(shù)據(jù)競爭。但有一個使用習慣要注意InitGoogleLogging和ShutdownGoogleLogging必須確保線程安全建議在main函數(shù)里單線程調用不要在全局對象構造或析構時觸發(fā)。FATAL日志是另一個坑。默認情況下LOG(FATAL)會打印棧信息然后調用abort()。在Windows VS的Debug配置下你會看到一個“中斷”彈窗大部分時候這個是有用的因為它幫你定位到了崩潰點。但在Release發(fā)布版里彈窗會影響用戶體驗。我一般這么處理google::InstallFailureFunction([]() { // 自定義崩潰回調比如把崩潰信息發(fā)送到日志服務器 std::cerr Fatal error occurred, see log for details std::endl; });這樣FATAL時不會abort得那么粗暴但注意你替換掉默認的abort行為后“崩潰前釋放資源、保存現(xiàn)場”這些邏輯需要你自己保證。如果你是做服務端程序建議還是保留默認abort讓守護進程來拉起來。還有一個問題在Windows上特別突出glog的FATAL信號捕獲對Windows的SEH結構化異常處理支持不如Linux下的POSIX信號好。也就是說如果你的程序是因為訪問空指針觸發(fā)的崩潰那走的是Windows異常處理機制glog默認的InstallFailureSignalHandler不一定會捕獲到反而LOG(FATAL)這種主動觸發(fā)的FATAL能正常走鉤子。這是個很多人不知道的差異。如果你的Windows程序要捕獲完整崩潰棧建議額外用Windows自己的SetUnhandledExceptionFilter或者把glog和breakpad一起用。6. 從能用到好用glog工程的進階配置與調試技巧6.1 多工程解決方案下如何統(tǒng)一配置glog很多人做項目不是一個工程而是有一個解決方案solution包含多個項目核心庫工程、業(yè)務庫工程、主程序工程、測試工程。如果每個工程都手動配一遍屬性維護成本很高。我的做法是用VS的屬性表Property Sheet統(tǒng)一管理。在解決方案里新建一個glog.props屬性表把這些配置寫進去?xml version1.0 encodingutf-8? Project ToolsVersion4.0 xmlnshttp://schemas.microsoft.com/developer/msbuild/2003 PropertyGroup IncludePath$(SolutionDir)third_party\glog\include;$(IncludePath)/IncludePath LibraryPath$(SolutionDir)third_party\glog\lib\$(Configuration)\$(Platform);$(LibraryPath)/LibraryPath /PropertyGroup ItemDefinitionGroup Link AdditionalDependenciesglog.lib;%(AdditionalDependencies)/AdditionalDependencies /Link /ItemDefinitionGroup /Project然后在每個工程里右鍵“添加現(xiàn)有屬性表”這個表就會應用到所有工程。以后升級glog版本只要替換目錄內容或改屬性表里的路徑不需要動工程代碼。這個做法在只有兩三個工程的時候顯得多余項目一多優(yōu)勢特別明顯。還有一點值得做在Debug|x64和Release|x64下分別設置預處理宏。由于glog的Debug庫名是glogd.lib我通常會在屬性表里用條件表達式區(qū)分Link AdditionalDependencies Condition$(Configuration)Debugglogd.lib;%(AdditionalDependencies)/AdditionalDependencies AdditionalDependencies Condition$(Configuration)Releaseglog.lib;%(AdditionalDependencies)/AdditionalDependencies /Link這樣一勞永逸不用每個配置來回切換著填庫名。6.2 用glog進行性能定位與線上問題排查日志除了記錄程序軌跡其實還承擔著一個重要職責性能定位。你可以在關鍵路徑前后記錄耗時用LOG(INFO)輸出時間戳配合glog自帶的時間信息就能大概判斷瓶頸。但注意日志本身也有性能開銷在for循環(huán)里每輪都打日志IO開銷會反過來拖慢程序。這時候LOG_EVERY_N和LOG_IF_EVERY_N就是救星。另外我推薦一個“統(tǒng)計型日志”的寫法在循環(huán)結束后一次性匯總打日志避免高頻日志的干擾。比如int successCount 0, failCount 0; for (...) { if (DoSomething()) successCount; else failCount; } LOG(INFO) Total: successCount failCount , success: successCount , fail: failCount;這樣在應用日志里看到的是干凈的匯總信息排查效率遠高于熬著一屏滾動的單條日志。線上排查場景里glog最有用的是ERROR和FATAL日志文件。我習慣在程序啟動時把LOG(ERROR)單獨輸出到error文件同時通過FLAGS_stderrthreshold讓控制臺只顯示W(wǎng)ARNING以上日志。這樣即使程序跑了一整天我只需要翻error日志就能定位問題不需要在幾十MB的INFO日志里大海撈針。6.3 日志序列化與自定義輸出glog的LOG(INFO) 重載支持一切可以通過ostream流輸出的類型但如果你要記錄一個自定義結構體就得自己重載operator。我在一個通信項目里記錄數(shù)據(jù)包時這么干過struct PacketHead { uint32_t seq; uint8_t type; uint16_t len; }; std::ostream operator(std::ostream os, const PacketHead head) { os [seq head.seq , type static_castint(head.type) , len head.len ]; return os; }這樣LOG(INFO) packetHead就能直接得到可讀的輸出不需要每次打日志都寫一段代碼拼字符串。這個習慣養(yǎng)成了日志代碼的整潔度會高很多。如果你想控制日志的格式細節(jié)比如去掉文件名、行號可以用google::SetLogFilenameExtension或自定義LogMessage的回調。不過大部分場景默認格式就夠了。7. 收尾我在實際項目中積累的幾條經(jīng)驗最后分享幾個我踩過坑后沉淀下來的小習慣。第一glog的頭文件在Windows下偶爾會和Windows.h有符號沖突尤其是如果你還引入了windows.h建議先包含glog/logging.h再包含其他頭文件或者使用WIN32_LEAN_AND_MEAN宏避免一堆無關的Windows定義。第二如果你在同一個程序中同時用了glog和gtest一定要注意它們可能都通過gflags暴露同名變量盡量減少gflags依賴或者在編譯glog時關掉它。第三發(fā)布程序時需要把glog的DLL或靜態(tài)庫一起帶上且要同時確認發(fā)布版本是Release編譯的否則客戶機器上會突然冒出來一堆_ITERATOR_DEBUG_LEVEL的錯誤。配置glog這件事本身不難但它涉及庫編譯、工程屬性、運行庫匹配、頭文件引入、動態(tài)庫部署等多個環(huán)節(jié)任何一個環(huán)節(jié)出錯報錯信息都容易讓人一頭霧水。我寫這篇博文的價值就是把這些錯誤和解決方案一次性擺出來你在從零配置的時候能少走很多彎路。按上面的順序操作一遍基本十分鐘內就能讓glog在VS的C工程里跑起來然后你就能享受一套踏實可靠的日志系統(tǒng)帶來的便利了。