1. 为什么要在VS里给C++项目引入glog库
先说下这玩意儿能解决什么问题。C++项目一旦跑起来,控制台里一堆调试输出,等程序崩了想查上下文,发现啥也没留下,那种感觉你应该不陌生。glog就是谷歌开源的那套日志库,专门解决“程序跑完只剩个黑框框”的尴尬。它在VS里的地位,基本相当于日志方案里的默认选项,不是因为它最炫,而是因为它最稳、最省心。
有人会说,我自己写个fprintf往文件里打不就行了吗?我当年也这么干过,后来发现写着写着就失控了。要加时间戳,要分级别,要按大小滚动,要崩溃时自动落盘,要支持多线程并发写日志,这些东西自己造轮子,没个几千行下不来。glog把这些全内置了,而且经过谷歌内部大规模验证,VS下直接引入,省的不只是开发时间,还有后面排查问题时的命。
这个方案适合谁?刚接触C++想在项目里加日志的初学者,也包括已经在用VS做中大型桌面端、工具链或服务器组件、想换掉自己那套土办法的开发者。不管你是用CMake管理项目,还是日常用.sln工程文件,下面这套流程都能直接落地。
2. 引入方案的选型:源码编译 vs vcpkg,为什么我推荐前者
2.1 两种主流引入方式对比
Visual Studio里接第三方库,大方向就两条路:一是用vcpkg这类包管理器,一条命令把依赖装好;二是手动下载源码、编译出lib和dll,再配到项目里。
| 方式 | 上手成本 | 可控性 | 适合场景 |
|---|---|---|---|
| vcpkg 集成 | 低,命令少 | 一般,库版本由清单控制 | 快速验证、依赖较多的项目 |
| 手动源码编译 | 中,需熟悉VS配置 | 高,可定制运行时库、静态/动态链接 | 发布产物体积敏感、需离线开发 |
我自己在VS里做项目,大多数情况下选源码编译。原因很简单:glog依赖gflags,vcpkg装的时候会拉一串依赖,虽然方便,但你不太清楚最终编出来的产物到底用了哪些运行时库、什么字符集、什么链接方式。等项目要发布给客户,或者在别的机器上重新编,版本对不上,就麻烦了。
2.2 什么时候直接用vcpkg反而更省事
如果你是在自己的学习项目或者内部工具里用glog,觉得“能跑就行、稳定就行”,那完全可以用vcpkg。2025年以后VS对vcpkg的集成已经做得非常透明,装好后直接include、链接,基本不用动脑子。
但如果你和我一样,维护的是要交付出去的商业项目,或者需要在离线环境下反复构建,那我强烈建议走手动编译这条路。原因后面在配置环节你就明白了——你真正控制的东西越多,后面才越安全。
3. 手动编译glog完整流程(VS2022实测)
3.1 准备阶段:拿到源码和依赖
编译glog前,先确认环境。我用的VS2022,社区版就够,安装时勾上“使用C++的桌面开发”,这个组件包含了MSVC编译器、Windows SDK和CMake工具,缺一不可。
然后下载glog源码,建议去GitHub官方仓库的releases页找稳定版,不要直接clone主干,主干偶尔会带些新特性但不太稳。我用的0.6.0版本,配合VS2022实测没有兼容问题。glog在Windows下还要用到gflags,同款方式下载,版本对齐官方README里写的就行。
有一步很容易被忽略:解压后看下目录里的CMakeLists.txt,顶部会有依赖项检查,如果系统里有vcpkg装了别的版本gflags,可能被自动关联过去。我更建议在编译glog之前,先把gflags源码也准备好,两个一起放进同一个第三方库管理目录,干净利落。
3.2 用CMake生成VS工程
glog官方不直接给你提供.sln,要用CMake先生成。打开VS2022的“开发者命令提示符”,或者直接用“x64 Native Tools Command Prompt for VS 2022”,进到glog源码目录,执行:
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX=D:/libs/glog-install这里的-S指定源码目录,-B指定构建目录,-G指定生成器,-A x64目标架构。CMAKE_INSTALL_PREFIX是后面install要装到的地方,这个路径自己规划好,我记得当年第一次就没设这个,结果装到默认路径,后面找文件找得头晕。
执行完这一步,build目录下会出现glog.sln。这里要特别留意,生成过程如果报找不到gflags的错,你得先确保gflags也编译并安装到同一个前缀下,或者在CMake命令里加一行:
-DCMAKE_PREFIX_PATH=D:/libs/gflags-installC++构建系统就是这样,依赖关系没理顺,后面全白搭。
3.3 编译与安装:Debug和Release都要编
接下来打开生成的sln,在VS里切换配置,先编译Release版本。选中glog项目,右键生成,等它跑完。然后切到Debug版本,再生成一次。两个都编,为的是你之后切配置时不用回头再编一次。
编完如果没报错,再右键INSTALL项目,生成。它会自动把头文件、lib、dll复制到CMAKE_INSTALL_PREFIX指定的目录。我建议你专门建一个本地libs目录,结构像这样:
D:/libs/ glog-install/ include/glog/logging.h lib/glog.lib lib/glog.dll gflags-install/ include/gflags/gflags.h lib/gflags.lib这个结构能让你在新项目里反复引用,不用每个项目都重新编译一遍。顺便说一句,如果你的工程是32位的,把前面CMake命令里的-A x64改成-A Win32,其他流程一样。
3.4 在VS项目里配置头文件与库路径
拿到编译产物后,在你自己项目的属性页里配置三项:
第一项,C/C++ -> 常规 -> 附加包含目录,把D:/libs/glog-install/include加进去。
第二项,链接器 -> 常规 -> 附加库目录,把D:/libs/glog-install/lib加进去。
第三项,链接器 -> 输入 -> 附加依赖项,填入glog.lib。如果链接时还报gflags相关的错误,同时把gflags.lib也加上。
这三项配好,常规的编译链接就能过了。但这里有个非常容易踩的坑:运行库设置。VS默认Debug可能用的/MTd,Release用/MT,而glog默认编出来的可能是/MD或/MDd。如果两边对不上,要么链接的时候报一堆LNK2038不匹配错误,要么运行的时候直接崩。解决方法是把你项目的“运行库”改得跟glog编译时一致,或者你回头重新编一份跟项目匹配的glog。我一般统一用/MT和/MTd,这样发布exe的时候不用带VC运行库。
4. 首次接入代码:从Hello World到正式使用
4.1 初始化与日志文件参数说明
先写一个最小示例验证是否引入成功:
#include <glog/logging.h> int main(int argc, char* argv[]) { google::InitGoogleLogging(argv[0]); LOG(INFO) << "glog initialized, this is an info message"; google::ShutdownGoogleLogging(); return 0; }InitGoogleLogging接收程序名,它会作为日志文件名的前缀。运行起来后,在Windows默认情况下,日志会输出到C:\Temp\程序名.INFO.log.日期-时间进程号这个路径下。如果没看到日志文件,可能是没有这个目录,先手动建一个C:\Temp,或者用下面代码指定日志目录:
FLAGS_log_dir = "D:/logs";FLAGS_log_dir是glog内置的全局参数,在输出前设置就行。除了这个,还有几个实用参数可以一并设置:
FLAGS_minloglevel = 0; // 0=INFO, 1=WARNING, 2=ERROR, 3=FATAL FLAGS_stderrthreshold = 2; // 把ERROR及以上级别同时输出到控制台 FLAGS_max_log_size = 100; // 单个日志文件最大100MB,超出自动滚动 FLAGS_colorlogtostderr = true; // 控制台输出带颜色,调试时好区分这些参数背后的逻辑我解释一下:minloglevel是设置输出日志的最低级别,你设成1之后,INFO日志直接丢弃,看着像“没输出”,其实是级别被过滤了。stderrthreshold则是把指定级别以上的日志同步打到stderr,这在你没有专门看文件、只在调试器里观察时特别有用。
4.2 日志宏的实用用法
glog的日志宏不像printf那么死板,它天然支持流式输出。实际项目里最常用的无非这么几个:
LOG(INFO) << "request received, user_id=" << user_id; LOG(WARNING) << "connection timeout, retry_count=" << retry_count; LOG(ERROR) << "parse config failed, error_code=" << err_code; // 记录堆栈信息,FATAL级别会在输出后终止程序 LOG(FATAL) << "unexpected null pointer, aborting";当你想记录某个关键分支是否执行到,可以用VLOG(1)这种冗长日志级别,它默认不输出,只有设置FLAGS_v=1时才打印。这特别适合塞一些调试细节到代码里,上线时保持静默,出问题再临时调日志级别,不用重新加日志重新编译。
LOG_IF和LOG_EVERY_N也是好东西。临时想看某条日志的时候,加个条件就行:
// 仅当HTTP状态码不等于200时记录 LOG_IF(ERROR, http_code != 200) << "http request failed, code=" << http_code; // 每第100次执行时打印一次,避免刷屏 LOG_EVERY_N(INFO, 100) << "processed " << count << " items";这些如果在VS输入时提示找不到宏,通常是头文件路径没配好或者宏定义放在命名空间里了,检查一下include顺序。
4.3 程序结束前的善后操作
程序结束时要调用:
google::ShutdownGoogleLogging();这不是可选项。不调用的话,缓冲区里没落盘的日志可能丢掉。尤其程序是直接return退出,或者中途exit(),日志缓冲区的数据全在内存里,没有正常flush。我见过有人在Windows服务里用了glog,关服务时总是丢最后几条日志,查了半天就是没调这个函数。
如果程序崩溃了,来不及调用ShutdownGoogleLogging怎么办?glog有个机制叫InstallFailureSignalHandler,注册常见信号的处理函数,崩溃时自动把当前缓冲日志落盘:
google::InstallFailureSignalHandler();在main函数顶部加这么一行,崩溃时日志完整性会好很多。这类细节官方文档里有,但很多教程不会特意讲,新手几乎都不知道。
5. 常见问题排查与避坑实录
5.1 编译链接期问题
| 报错信息 | 原因 | 解决方案 |
|---|---|---|
| LNK2038 运行时库不匹配 | 项目运行库设置与glog编译时不一致 | 统一运行库为/MT或/MD,或重编glog |
| LNK2001 无法解析的外部符号google::InitGoogleLogging | 没链接glog.lib或lib顺序不对 | 检查附加依赖项,确保库路径正确 |
| C1083 打不开glog/logging.h | 附加包含目录配置错误 | 核对include路径是否指向glog安装目录 |
LNK2038是我见过最多的。前阵子帮朋友解决一个项目,他glog是网上别人编好直接拷给他的,自己也没问人家用的什么运行库,结果Debug版程序一连就报错。最后让他去查glog生成时的CMake配置,重新用/MTd编了一遍才通。所以还是那句话:自己编译能避开很多隐性坑。
5.2 运行期日志不输出、不落盘
编译链接都通过了,程序跑起来却看不到日志文件,十有八九是这三个原因:
第一个,日志目录不存在。Windows下glog默认写到C:\Temp,这个目录系统不会自动给你建。先手动建目录,或者代码里FLAGS_log_dir指定一个已经存在的目录。
第二个,日志级别被过滤。FLAGS_minloglevel设成了1而你又只打INFO,那自然看不到。把FLAGS_minloglevel设成0再看。
第三个,日志缓冲没刷。glog默认是有缓冲的,程序还在跑的时候有些日志没及时写进文件。等一会儿再看,或者临时设FLAGS_logbufsecs=0禁用缓冲,日志就实时落盘了。
5.3 多线程与性能问题
glog内部用了锁保证多线程写日志不交错,所以你在多线程程序里直接用LOG(INFO)是线程安全的。不过要注意,高并发下频繁打日志会有锁竞争,性能会下降。我的建议是,业务代码里不要每条请求都打日志,改成采样或者只打WARNING以上级别。曾经我在一个服务类项目里,上线前把所有VLOG(1)和INFO级别日志关掉,线上QPS直接翻了一倍,深有感触。
如果确实需要高性能日志,可以考虑在release模式下把日志级别统一抬到WARNING,或者用LogMessage的延迟求值特性,这样参数表达式只有在实际要记录时才会被计算。
5.4 中文乱码与编码问题
在Windows下用glog输出中文,经常会控制台乱码或者日志文件里乱码。原因是VS源码文件默认GBK编码,但glog内部按UTF-8处理。我建议源码文件统一用UTF-8编码保存,并且路径和日志内容都保持UTF-8。Visual Studio里可以在“文件->另存为->编码”里手动调整,也可以装个插件强制新文件用UTF-8,会省掉很多烦恼。
6. 在VS中引入glog后的项目组织建议
6.1 工程里的封装与二次封装
直接把glog原生的LOG(INFO)撒得满项目都是,后续想换日志库或者做统一格式,会很痛苦。所以我在自己的VS项目里通常做一层薄封装:
// logger.h #pragma once #include <glog/logging.h> #define LOG_INFO LOG(INFO) #define LOG_WARN LOG(WARNING) #define LOG_ERROR LOG(ERROR)不要小看这一层宏,真到要调整日志格式或者切库时,你只改头文件里的宏定义,不用全局替换。另一个实践是给日志统一加“模块名”前缀,排查问题时能快速定位是哪个模块打的:
#define MODULE_LOG(module, level) \ LOG(level) << "[" << module << "] "这样写日志时带上模块名,比如MODULE_LOG("network", INFO) << "connect ok",日志文件里就清晰多了。
6.2 静态库还是动态库的选择
如果你把glog以动态库形式引入,那么发布程序时要记得把glog.dll一并放到exe同目录下,否则客户机器上报“找不到glog.dll”。这就是我之前说的,很多没经验的开发者容易忽略部署这一步。
我的做法是,在交付给客户的项目里,统一用静态库链接。CMake编译glog时,加一个选项:
-DBUILD_SHARED_LIBS=OFF这样就可以编出静态库。静态链接的好处是exe不依赖外部dll,部署简单,也不会被系统中另外一个同名dll干扰。缺点就是exe体积会大一些,但几百KB的增量在现代机器上根本不算什么。
6.3 日志文件的定期清理
glog虽然支持单文件滚动和大小限制,但不会自动删除旧日志。长时间运行的程序,日志目录会越来越肥。我一般在项目里加个启动清理逻辑:遍历日志目录,删除7天以前的.log文件。这不算glog的功能,算你工程里该有的自洁能力,但很多人忽略它。你可以用Windows API的FindFirstFile和FindNextFile遍历删除,或者引入std::filesystem在C++17下写,都很简单。
7. 后续扩展:glog和其他日志库对比
可能有人会问,既然glog在VS里这么好用,那跟spdlog、log4cplus比,该怎么选?
glog最大的优势是Google出品,底层成熟,崩溃信号处理、条件日志这种细节都帮你想到了,在大型服务端项目里经历过验证。而spdlog是纯头文件库,上手更快,性能也不错,风格上更现代,支持格式化语法。log4cplus则是Java社区log4j风格的C++移植,老牌,但配置更繁琐。
| 对比项 | glog | spdlog | log4cplus |
|---|---|---|---|
| 上手难度 | 中 | 低 | 高 |
| 崩溃信号处理 | 内置 | 需自己处理 | 需自己处理 |
| 性能 | 高 | 极高 | 中 |
| Windows下适配度 | 高(VS可直接编译) | 高 | 中 |
| 配置复杂度 | 简单 | 简单 | 复杂 |
如果你追求稳定和成熟,选glog;如果你追求轻量快速且都是新项目,spdlog也可以考虑。但如果在VS里集成遇到问题想有人解答,glog的社区资料量和Stack Overflow回答量远多于其他库,这也是我推荐它作为第一选择的原因。
8. 踩坑总结与我的实操心得
如果只让你记住一点,那就是:尽量自己编译glog,并把运行库统一。我在VS里接各种C++库,百分之八十的链接问题最后都归到“运行库不一致”这一个大类。
另一点心得是,日志这个东西,一定要在项目最早期就接入,不要等到项目大了再补。glog本身改动成本不高,但补日志时你得去翻老代码,看哪些地方该打日志、该打什么级别,那才是真正耗时的地方。项目一开始就把它放在工程基座里,后面所有模块开发时顺手打日志,省的是整个团队的时间。
还有个小细节:在VS里调试时,如果日志文件总是被占用无法打开,多半是你程序还在跑,日志句柄没释放。检查下是不是忘了调ShutdownGoogleLogging,或者程序没正常退出。我碰到过几次,最后发现是关调试器时进程没杀干净,任务管理器手动结束就好了。
再分享一个我常用的调试技巧。开发阶段把FLAGS_log_dir指到项目目录下的logs文件夹,但是把这个文件夹加进.gitignore,不要让日志进版本库。出错时别人拉代码、跑程序就能直接复现日志,不用到处问日志在哪。
glog在VS里跑通之后,你会发现排查问题的效率提升是实打实的。以前靠断点一步步猜、靠注释printf,现在直接看崩溃前的日志,哪一步出了问题一目了然。这套流程我第一次配置用了差不多半天,主要时间花在理解CMake生成和VS配置上;第二次再接新项目,五分钟就搞定。希望这篇也能帮你把配置时间压缩到最短。