简介:这份资源是面向Windows平台C++开发者的glog日志库集成包,适合需要在VS2017环境下为项目引入高效日志系统的中高级开发者。glog作为Google开源的跨平台日志工具,支持INFO、WARNING、ERROR、FATAL多级别输出、堆栈回溯、VLOG调试日志及日志速率限制等特性,能有效支撑大规模系统的运行状态跟踪与异常排查。压缩包共13个文件,约80KB,包含5个cmake构建脚本、5个h头文件、1个lib静态库、1个dll动态库及1个pc配置文件,覆盖编译配置、接口声明与链接所需的核心组件,便于直接集成到现有工程。目前已有299人学习下载,读者可借此快速完成glog在Windows下的编译与接入,掌握日志初始化、级别控制与输出定制等关键用法,提升问题定位效率。
1. glog for windows:把 Linux 上跑顺的日志库搬到 Windows 到底卡在哪
如果你在 Linux 下写过 C++ 服务,大概率用过 glog:LOG(INFO) << "xxx"一行就能打出带文件名、行号、时间戳、线程 ID 的日志,还能按级别过滤、按大小滚动、崩溃时自动 dump。可一旦把同一份代码挪到 Windows 上编译,第一反应往往是——CMake 报错、unistd.h找不到、链接缺符号、跑起来日志文件路径全是反斜杠还带乱码。glog for windows 这个标题背后,真正要解决的不是“能不能编译”,而是“怎么在 MSVC 工具链下把它编成一个稳定、可复用、行为跟 Linux 尽量一致的静态库,并让业务代码无感迁移”。
这篇面向的是正在做跨平台 C++ 项目的同学:你可能已经有一份 Linux 上验证过的日志方案,现在要在 Windows 上落地;也可能刚接手一个 Windows 端的模块,需要一套比std::cout和手写fprintf更靠谱的日志设施。我会按“先讲清 glog 在 Windows 上的适配现状 → 用 CMake 编出库 → 接进业务代码 → 参数怎么调 → 踩过哪些坑 → 进阶技巧”的顺序讲,每一步都给可复制的命令和代码。读完你应该能自己在一个干净的 Windows 环境里把 glog 跑起来,并且知道哪些行为跟 Linux 不一样、为什么不一样。
2. 先搞清楚 glog 在 Windows 上的适配现状与选型理由
2.1 glog 的跨平台设计:哪些代码是平台相关的
glog 本身从设计上就是跨平台的,源码里大量用宏把平台差异隔离开。核心的平台相关部分集中在几个地方:文件操作(open/write/close在 Windows 上对应_open/_write/_close)、线程 ID 获取(Linux 用syscall(SYS_gettid),Windows 用GetCurrentThreadId)、时间获取(gettimeofdayvsGetSystemTimeAsFileTime)、以及符号解析(Linux 用backtrace,Windows 用StackWalk或CaptureStackBackTrace)。这些差异在 glog 源码里通过#ifdef _WIN32分支处理,所以理论上你不需要改源码,只需要用正确的工具链编译。
但“理论上”和“实际上”之间隔着几个现实问题。第一,glog 的构建系统经历过从 autotools 到 CMake 的迁移,老版本在 Windows 上 CMake 支持不完整,新版本才比较顺。第二,MSVC 和 MinGW 两条工具链的行为不一样,MSVC 下某些 POSIX 函数的替代实现有细微差别。第三,glog 依赖 gflags 做命令行参数解析(虽然可以关掉),而 gflags 在 Windows 上也有自己的适配问题。所以选型时要先确认:你用的是哪个版本的 glog、哪个编译器、要不要 gflags。
常见做法是直接用 CMake 构建,关掉不必要的外部依赖,把 glog 编成静态库链进项目。我一般会优先选 MSVC 工具链,因为 Windows 上大部分 C++ 项目最终都要跟 MSVC 运行时打交道,MinGW 虽然更接近 Linux 习惯,但在跟 Windows SDK 交互时容易出问题。
2.2 版本选择:为什么建议用较新的 CMake 构建版本
glog 的版本演进中,构建系统的变化比 API 变化更值得关注。早期版本用 autotools,Windows 上基本没法直接用;后来引入 CMake,但早期 CMake 脚本对 MSVC 的支持有不少硬编码假设,比如默认按 Unix 路径处理、没处理NOMINMAX宏冲突等。较新的版本在 CMake 脚本里加了if (WIN32)分支,处理了dbghelp.lib链接、_WIN32_WINNT宏定义、以及unistd.h的条件包含。
选版本时不要盲目追最新,但也不要用过老的。判断标准很简单:看它的 CMakeLists.txt 里有没有针对 WIN32 的显式处理。如果没有,你在 Windows 上编译大概率要自己打补丁。另外注意,glog 从某个版本开始把 gflags 变成了可选依赖,如果你不需要命令行参数解析,可以在 CMake 里关掉,少一个依赖少一堆麻烦。
提示:不要从包管理器(如 vcpkg)直接装 glog 然后指望它跟你的 MSVC 版本完全匹配。包管理器编出来的库用的运行时可能跟你的项目不一致,导致链接时出现
LNK2038运行时不匹配错误。自己用 CMake 编一遍最稳。
2.3 工具链准备:MSVC、CMake、Git 三件套
在 Windows 上编译 glog,你需要三样东西:一个 C++ 编译器(MSVC 或 MinGW)、CMake、以及获取源码的方式(Git 或直接下载压缩包)。MSVC 建议用 Visual Studio 2019 或 2022 的 Build Tools,不一定要装完整 IDE,但要有 MSVC 编译器和 Windows SDK。CMake 建议 3.15 以上,因为 glog 的 CMake 脚本用了一些较新的特性。
安装完确认环境变量:cl.exe能在命令行找到(或者你知道怎么用vcvarsall.bat初始化环境),cmake --version能输出版本号。如果你用 PowerShell,注意vcvarsall.bat是 cmd 脚本,需要先cmd /c调用或者直接在 “x64 Native Tools Command Prompt” 里操作。这一步看着简单,但很多人卡在“CMake 找不到编译器”上,本质就是环境没初始化。
3. 用 CMake 在 Windows 上编出 glog 静态库
3.1 获取源码与目录结构确认
先拿到源码。假设你已经把 glog 源码放在D:\third_party\glog下,目录里应该能看到CMakeLists.txt、src、cmake等。确认一下CMakeLists.txt里有没有WIN32相关处理,如果没有,说明版本偏老,建议换一个。
# 在源码目录外建一个 build 目录,保持源码干净 cd /d D:\third_party\glog mkdir build && cd build # 用 CMake 生成 MSVC 工程,指定安装前缀 cmake .. -G "Visual Studio 17 2022" -A x64 ^ -DCMAKE_INSTALL_PREFIX=D:/third_party/glog-install ^ -DWITH_GFLAGS=OFF ^ -DBUILD_SHARED_LIBS=OFF ^ -DBUILD_TESTING=OFF这段命令的逻辑:-G指定生成器为 VS2022,-A x64指定 64 位;CMAKE_INSTALL_PREFIX是安装目录,后面业务项目要指向这里;WITH_GFLAGS=OFF关掉 gflags 依赖,因为大多数场景不需要;BUILD_SHARED_LIBS=OFF编静态库,避免 DLL 分发问题;BUILD_TESTING=OFF跳过测试,加快编译。参数不是死的,如果你需要 gflags 就把它打开,但记得先把 gflags 也编好。
3.2 编译与安装:Release 和 Debug 要分开
CMake 生成的是多配置工程,默认 Debug 和 Release 是分开的。建议两个都编,因为业务项目 Debug 和 Release 用的库不一样,混用会出运行时错误。
# 编译 Release 并安装 cmake --build . --config Release --target install # 编译 Debug 并安装到同一前缀(glog 会自动区分 debug/release 子目录) cmake --build . --config Debug --target install编译完成后,去D:\third_party\glog-install看目录结构:include/glog下是头文件,lib下是glog.lib(Release)和glogd.lib(Debug,名字可能带 d 后缀)。如果lib下只有一个配置的库,说明安装时覆盖了,需要检查 CMake 的安装规则。有些版本的 glog 会把 Debug 和 Release 装到同一目录但文件名不同,有些会分debug/lib和release/lib,以实际为准。
注意:MSVC 的 Debug 和 Release 运行时库不兼容,Debug 版 glog 必须配 Debug 版业务代码,Release 同理。链接时如果报
LNK2038: 检测到“RuntimeLibrary”的不匹配项,就是这个问题。
3.3 验证库文件:写一个最小可执行程序
编完库别急着接业务,先写个最小程序验证。新建test_glog.cpp:
#include <glog/logging.h> int main(int argc, char** argv) { // 初始化 glog,argv[0] 用作日志文件前缀 google::InitGoogleLogging(argv[0]); // 日志输出到当前目录,按级别分文件 FLAGS_logtostderr = false; FLAGS_log_dir = "."; FLAGS_minloglevel = 0; // 输出 INFO 及以上 LOG(INFO) << "glog on windows works"; LOG(WARNING) << "this is a warning"; LOG(ERROR) << "this is an error"; // 关闭前刷新 google::ShutdownGoogleLogging(); return 0; }用 CMake 或直接命令行编译。命令行方式(在 VS 开发者命令行里):
cl /EHsc /I D:\third_party\glog-install\include ^ test_glog.cpp ^ /link /LIBPATH:D:\third_party\glog-install\lib glog.lib ^ dbghelp.lib/EHsc启用标准 C++ 异常处理,glog 头文件需要;/I指定头文件路径;/link /LIBPATH指定库路径;dbghelp.lib是 Windows 上符号解析需要的系统库,glog 在 Windows 下依赖它做堆栈跟踪。编译成功后运行,当前目录应该出现test_glog.INFO之类的日志文件。如果报找不到glog.lib,检查路径和配置是否匹配。
4. 把 glog 接进业务代码:初始化、参数与日志滚动
4.1 初始化时机与线程安全
glog 的初始化必须在任何LOG宏之前调用,而且最好在主线程启动早期完成。InitGoogleLogging不是线程安全的,如果多个线程同时初始化会出问题。常见做法是在main函数开头调用,或者在动态库的初始化函数里调用一次。
#include <glog/logging.h> int main(int argc, char** argv) { // 必须在所有 LOG 之前 google::InitGoogleLogging(argv[0]); // 设置日志目录,Windows 下路径用正斜杠或双反斜杠 google::SetLogDestination(google::INFO, "D:/logs/app_"); google::SetLogDestination(google::WARNING, "D:/logs/warn_"); google::SetLogDestination(google::ERROR, "D:/logs/err_"); // 设置日志文件扩展名 google::SetLogFilenameExtension(".log"); // 业务代码 LOG(INFO) << "service started"; google::ShutdownGoogleLogging(); return 0; }SetLogDestination的第二个参数是文件前缀,glog 会自动在后面拼接主机名、用户名、时间戳、级别和扩展名。Windows 下路径分隔符建议用正斜杠,glog 内部会处理,用反斜杠要注意转义。SetLogFilenameExtension改的是最终扩展名,默认是.log加级别名,比如.INFO。
4.2 常用 FLAGS 参数:哪些该在 Windows 上特别关注
glog 的行为大量依赖全局 FLAGS,这些参数在 Windows 上有几个需要特别注意:
| 参数 | 作用 | Windows 注意事项 |
|---|---|---|
FLAGS_log_dir | 日志目录 | 路径含空格时要用引号,建议用正斜杠 |
FLAGS_logtostderr | 输出到 stderr | Windows 控制台编码可能影响中文显示 |
FLAGS_alsologtostderr | 同时输出到文件和 stderr | 调试时有用,生产环境关掉 |
FLAGS_minloglevel | 最低输出级别 | 0=INFO,1=WARNING,2=ERROR,3=FATAL |
FLAGS_max_log_size | 单文件最大 MB | 超过后滚动,Windows 下文件锁行为不同 |
FLAGS_stop_logging_if_full_disk | 磁盘满时停止 | Windows 下磁盘满判断可能滞后 |
设置方式有两种:代码里直接赋值FLAGS_log_dir = "D:/logs";,或者通过命令行参数(如果编译时开了 gflags)。代码里赋值更直接,但要注意在InitGoogleLogging之后、第一条日志之前设置。
4.3 日志滚动与清理:Windows 文件锁的坑
glog 的日志滚动按FLAGS_max_log_size触发,达到大小后关闭当前文件、开新文件。Linux 下这很顺,Windows 下有个坑:如果另一个进程(比如日志采集工具)正打开着日志文件,glog 尝试重命名或删除时会失败,导致滚动卡住。解决办法是确保没有外部进程长期持有日志文件句柄,或者用支持共享读的采集方式。
// 设置单文件 10MB,滚动 FLAGS_max_log_size = 10; // 设置日志保留时间(天),需要配合自定义清理逻辑 // glog 本身不自动删旧日志,需要自己写定时任务glog 不会自动删除旧日志文件,长期运行的服务需要自己加清理逻辑。常见做法是起一个低优先级线程,定期扫描日志目录,删除超过 N 天的文件。Windows 下删文件前要确认文件没被占用,可以用CreateFile带FILE_SHARE_DELETE打开测试,或者直接DeleteFile失败就跳过。
5. glog for windows 避坑记录:五个真实翻车现场
5.1 现象:编译报错unistd.h找不到
原因:老版本 glog 在 Windows 分支里没有完全屏蔽 POSIX 头文件,或者 CMake 配置没正确定义_WIN32。解决:升级到较新版本,确认CMakeLists.txt里有if (WIN32)处理;如果必须用老版本,手动在包含unistd.h的地方加#ifndef _WIN32保护。
5.2 现象:链接报错LNK2038 RuntimeLibrary 不匹配
原因:Debug 版业务代码链了 Release 版 glog,或者反过来。MSVC 的/MDd和/MD运行时库不兼容。解决:确认业务项目的配置和 glog 库的配置一致,Debug 配 Debug,Release 配 Release。CMake 里用debug和optimized关键字分别指定。
5.3 现象:日志文件里中文乱码
原因:Windows 控制台默认代码页是 GBK,而 glog 输出的是 UTF-8 或者反过来。解决:统一用 UTF-8。源码文件保存为 UTF-8 with BOM(MSVC 需要 BOM 才认),或者在main开头调用SetConsoleOutputCP(CP_UTF8)。日志文件本身建议用 UTF-8,查看时用支持 UTF-8 的编辑器。
5.4 现象:LOG(FATAL)没有生成 dump 文件
原因:Windows 下 glog 的崩溃处理依赖SetUnhandledExceptionFilter,如果业务代码自己设置了异常处理,会覆盖 glog 的。解决:要么不用自己的异常处理,要么在自定义处理里先调用 glog 的。另外确认FLAGS_symbolize_stacktrace相关设置,Windows 下符号化需要dbghelp.dll在 PATH 里。
5.5 现象:多线程下日志丢失或交错
原因:glog 本身是线程安全的,但如果你在ShutdownGoogleLogging之后还有线程在写日志,行为未定义。解决:确保所有线程停止后再调用ShutdownGoogleLogging。另外,FLAGS_logtostderr在多线程下输出到控制台可能交错,这是控制台本身的限制,不是 glog 的问题。
6. 进阶:把 glog 封装成项目内统一的日志门面
直接到处写LOG(INFO)有个问题:业务代码跟 glog 绑死了,将来换日志库要改遍全项目。更好的做法是包一层薄门面,把 glog 的宏藏起来,对外暴露自己的接口。这样既保留了 glog 的能力,又留了替换余地。
// logger.h - 项目内统一日志接口 #pragma once #include <string> namespace myapp { enum class LogLevel { kInfo, kWarning, kError, kFatal }; class Logger { public: // 初始化,在 main 开头调用一次 static void Init(const std::string& app_name, const std::string& log_dir); // 关闭,在 main 返回前调用 static void Shutdown(); // 通用写日志接口,内部转发给 glog static void Write(LogLevel level, const char* file, int line, const std::string& msg); }; } // namespace myapp // 对外宏,业务代码用这个,不直接碰 glog #define MYAPP_LOG(level, msg) \ do { \ std::ostringstream oss; \ oss << msg; \ myapp::Logger::Write(level, __FILE__, __LINE__, oss.str()); \ } while (0) #define MYAPP_INFO(msg) MYAPP_LOG(myapp::LogLevel::kInfo, msg) #define MYAPP_WARN(msg) MYAPP_LOG(myapp::LogLevel::kWarning, msg) #define MYAPP_ERROR(msg) MYAPP_LOG(myapp::LogLevel::kError, msg)实现文件里把Logger::Write转发到 glog 的LOG(LEVEL),同时可以做额外处理,比如加请求 ID、脱敏、限流。这样业务代码只依赖logger.h,将来换 spdlog 或自己写文件输出,只改实现文件。
验证封装是否生效,可以写个简单测试:初始化后打几条不同级别的日志,检查文件是否按级别分开、格式是否包含文件名和行号。再模拟一个多线程场景,起 4 个线程各打 1000 条,看有没有丢失或交错。如果都正常,这套封装就可以进项目了。
我自己的习惯是:任何第三方库进项目,先包一层再用的,后面换库或者加定制逻辑时省下的时间远超封装成本。glog 在 Windows 上虽然有些小脾气,但编好之后稳定性没问题,值得花半天时间把它跑通、封好。希望帮到你。
本文还有配套的精品资源,点击获取