ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

glog for Windows:跨平台日志库的编译、初始化与避坑指南

glog for Windows:跨平台日志库的编译、初始化与避坑指南 简介这份资源是面向 Windows 平台 C 开发者的 glog 日志库集成包适合需要在 VS2017 项目中引入高效日志系统的中高级开发者。glog 是 Google 开源的跨平台日志工具支持 INFO、WARNING、ERROR、FATAL 多级别输出与堆栈回溯能帮助排查程序异常、分析运行状态。压缩包共 13 个文件约 80KB包含 5 个 cmake 构建脚本、5 个 h 头文件、1 个 lib 静态库、1 个 dll 动态库及 1 个 pc 配置文件覆盖编译、链接与 pkgconfig 引用所需的核心组件。目前已有 298 人学习下载。通过该资源读者可直接获得可用的库文件与头文件省去自行编译依赖的步骤快速在 Windows 工程中完成日志模块接入并结合 VLOG、日志速率限制等特性优化调试与性能分析流程。1. glog for windows把 Linux 上那套日志习惯搬进 Windows 工程如果你长期在 Linux 下写 C大概率对 glog 不陌生——LOG(INFO)、CHECK_EQ、VLOG这些宏几乎是肌肉记忆。可一旦项目要交付到 Windows很多人第一反应是换 spdlog 或者干脆用OutputDebugString凑合结果日志格式、级别控制、崩溃现场全乱套。glog for windows 这个资源解决的正是这件事让 glog 在 MSVC 工具链下正常编译、正常输出、正常滚动不用为了跨平台再养一套日志代码。它适合三类人一是手里有 Linux 存量代码、需要移植到 Windows 的 C 工程师二是做 Windows 桌面端或服务端、想要结构化日志而不是printf的开发者三是需要把日志接入现有构建系统CMake / vcpkg / MSBuild的团队。资源本身是 glog 在 Windows 平台的适配与构建产物核心价值在于把「Linux 能跑」变成「Windows 也能跑而且行为一致」。2. 先搞清楚 glog 在 Windows 上到底卡在哪2.1 glog 的日志模型与 Windows 的差异点glog 的设计假设里有一批 Unix 味道很重的东西它默认用pthread做线程局部存储用syslog风格的严重级别用gflags解析命令行参数文件滚动依赖unistd.h里的access、unlink。搬到 Windows 上这些全要换实现。资源里做的适配主要集中在这几块线程局部存储改用__declspec(thread)或TlsAlloc文件操作走_access/_unlink/CreateFile时间戳用GetSystemTimeAsFileTime替代gettimeofday。另一个容易被忽略的点是路径分隔符。glog 生成日志文件名时会拼program.hostname.user.log.INFO.日期.进程号在 Windows 上如果直接拿argv[0]当程序名可能带.exe后缀甚至完整路径导致文件名里出现冒号和反斜杠CreateFile直接失败。资源里对ProgramInvocationName做了清洗只取 basename 并去掉扩展名。还有符号导出问题。glog 编译成动态库时Windows 默认不导出任何符号必须显式加__declspec(dllexport)或者用.def文件。资源里通过GLOG_EXPORT宏配合GLOG_IS_DLL开关处理静态库和动态库两种模式都能编。2.2 构建方式选型CMake 直编还是 vcpkg拿到资源后第一件事是决定怎么编。常见做法有三种我一般按项目现状选方式适用场景优点注意点CMake 源码直编已有 CMake 工程想控制编译选项灵活能改源码要自己处理依赖和导出宏vcpkg 安装新项目依赖统一管理一条命令搞定版本可能滞后定制困难预编译库只想快速接入不改 glog省时间要匹配 MSVC 版本和运行库资源本身带 CMakeLists所以直编是最稳的。下面给一个我常用的构建命令假设源码在third_party/glog# 在工程根目录执行生成 VS 工程 cmake -S third_party/glog -B build/glog \ -G Visual Studio 17 2022 -A x64 \ -DWITH_GFLAGSOFF \ -DWITH_UNWINDOFF \ -DBUILD_SHARED_LIBSOFF \ -DCMAKE_MSVC_RUNTIME_LIBRARYMultiThreadedDebugDLL # 编译 Release 和 Debug 两个配置 cmake --build build/glog --config Debug cmake --build build/glog --config ReleaseWITH_GFLAGSOFF是因为 Windows 上 gflags 又是一套额外依赖除非你确实要用命令行控制日志级别否则关掉能少踩很多坑。WITH_UNWINDOFF是因为libunwind在 Windows 上基本不可用开了也编不过。BUILD_SHARED_LIBSOFF建议静态链接省得运行时还要拷 DLL。CMAKE_MSVC_RUNTIME_LIBRARY必须和主工程一致否则会出现LNK2038运行库不匹配的链接错误——这个坑我后面还会细说。编完之后产物在build/glog/Debug和build/glog/Release下头文件在third_party/glog/src。主工程里这样接# 主工程 CMakeLists.txt 片段 add_subdirectory(third_party/glog) target_include_directories(your_target PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/glog/src ${CMAKE_CURRENT_BINARY_DIR}/glog) # 生成的 glog/logging.h 配置头 target_link_libraries(your_target PRIVATE glog::glog)注意glog/logging.h里会 include 一个生成的glog/logging.h配置头在 build 目录下所以 include 路径必须同时包含源码目录和构建目录少一个就报找不到头文件。3. 把 glog 接进 Windows 工程初始化、输出与滚动3.1 初始化参数怎么设才不翻车glog 在 Windows 上初始化比 Linux 多几个必设项。最典型的是FLAGS_logtostderr和FLAGS_alsologtostderrLinux 下默认写文件Windows 下如果没设目录日志会散落在当前工作目录而 Windows 服务的当前目录经常是C:\Windows\System32写不进去就静默失败。我一般强制指定日志目录#include glog/logging.h #include filesystem int main(int argc, char** argv) { // 必须最先调用解析 argv[0] 作为程序名 google::InitGoogleLogging(argv[0]); // 日志目录不存在就创建 std::filesystem::path log_dir D:/logs/myapp; std::filesystem::create_directories(log_dir); // 设置日志文件前缀目录 google::SetLogDestination(google::INFO, (log_dir / INFO_).string().c_str()); google::SetLogDestination(google::WARNING, (log_dir / WARN_).string().c_str()); google::SetLogDestination(google::ERROR, (log_dir / ERROR_).string().c_str()); // 同时输出到 stderr方便调试 FLAGS_alsologtostderr true; // 日志文件保留天数0 表示不删除 FLAGS_log_days_to_keep 7; // 每条日志都 flush避免崩溃丢日志 FLAGS_logbufsecs 0; LOG(INFO) glog initialized on Windows; LOG(WARNING) this is a warning; LOG(ERROR) this is an error; google::ShutdownGoogleLogging(); return 0; }InitGoogleLogging必须传argv[0]否则 glog 拿不到程序名日志文件名会变成unknown。SetLogDestination的第二个参数是前缀glog 会在后面拼主机名、用户名、日期、进程号。FLAGS_logbufsecs 0是关键——默认 glog 会缓冲 30 秒再写盘Windows 上程序崩溃时缓冲区直接丢设成 0 每条都 flush代价是性能略降但排查崩溃时这点性能换命。FLAGS_log_days_to_keep是 glog 0.5 之后才有的老版本没有这个 flag设了会报未知参数。资源里如果带的是老版本滚动清理要自己写或者用SetLogFilenameExtension配合外部脚本。3.2 日志滚动与文件命名在 Windows 下的实际表现glog 的滚动策略是「按级别 按天」不是按大小。也就是说同一天内日志文件会一直追加直到跨天或者进程重启。文件名格式大致是INFO_主机名_用户名_20250101-120000.12345.logWindows 主机名可能带中文或特殊字符如果机器名是中文CreateFile用 ANSI 版本会失败。资源里如果用的是CreateFileA遇到中文主机名就写不出日志。解决办法是编译时加UNICODE和_UNICODE让 glog 走宽字符路径或者手动SetLogDestination到一个纯英文前缀绕开主机名拼接。另一个实际问题是多进程写同一目录。glog 的文件名带进程号所以多进程不会互相覆盖但log_days_to_keep清理时是按文件名模式匹配的如果多个进程的日志混在一个目录清理逻辑可能误删别的进程日志。我一般按进程或按服务实例分目录std::string dir D:/logs/myapp/ std::to_string(GetCurrentProcessId()); google::SetLogDestination(google::INFO, (dir /INFO_).c_str());这样每个进程独立目录清理互不影响。代价是目录多但排查时反而清晰。3.3 崩溃时的日志落盘InstallFailureSignalHandler在 Windows 的局限Linux 下 glog 提供InstallFailureSignalHandler捕获 SIGSEGV、SIGABRT打印堆栈后退出。Windows 没有这些信号对应的是结构化异常SEH和SetUnhandledExceptionFilter。glog 在 Windows 上对这块支持有限InstallFailureSignalHandler基本是空实现或者只处理SIGABRT。所以 Windows 上要抓崩溃现场常见做法是自己挂SetUnhandledExceptionFilter在回调里调google::FlushLogFiles把缓冲区刷盘再用CaptureStackBackTrace或StackWalk64打堆栈#include windows.h #include glog/logging.h LONG WINAPI CrashHandler(EXCEPTION_POINTERS* info) { // 先把 glog 缓冲区刷盘否则最后几条日志丢失 google::FlushLogFiles(google::GLOG_INFO); LOG(ERROR) crash exception code: 0x std::hex info-ExceptionRecord-ExceptionCode; // 这里可以接 MiniDumpWriteDump 生成 dump return EXCEPTION_EXECUTE_HANDLER; } int main(int argc, char** argv) { google::InitGoogleLogging(argv[0]); SetUnhandledExceptionFilter(CrashHandler); // ... }FlushLogFiles必须在写崩溃日志之前调因为崩溃时堆可能已经损坏再走LOG宏有二次崩溃风险。更稳的做法是崩溃回调里只用WriteFile直接写原始字符串不碰 glog 的堆分配。这个取舍看你对崩溃日志完整性的要求。4. 避坑与排查Windows 上编 glog 最容易翻车的五件事4.1 运行库不匹配导致 LNK2038现象链接时报LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MT_StaticRelease”不匹配值“MD_DynamicRelease”。原因glog 编译时用的运行库和主工程不一致。CMake 默认可能生成/MT而 VS 工程默认/MD。解决编译 glog 时显式指定-DCMAKE_MSVC_RUNTIME_LIBRARYMultiThreadedDLLRelease或MultiThreadedDebugDLLDebug和主工程对齐。如果主工程是/MT就改成MultiThreaded/MultiThreadedDebug。两边必须一模一样Debug 配 DebugRelease 配 Release。4.2 找不到glog/logging.h现象fatal error C1083: 无法打开包括文件: “glog/logging.h”。原因只加了源码目录没加构建目录。glog 的logging.h会 include 一个由 CMake 生成的配置头那个头在 build 目录下。解决include 路径同时加third_party/glog/src和build/glog或build/glog/Debug取决于生成器。用target_include_directories时把两个都写上别只写一个。4.3 日志文件写不出来程序却正常退出现象LOG(INFO)执行了但目标目录没有文件程序也不报错。原因Windows 服务的当前工作目录是System32没权限写或者SetLogDestination的路径里有中文CreateFileA失败或者FLAGS_logbufsecs默认 30 秒程序退出太快没来得及 flush。解决第一用绝对路径且确保目录存在第二路径避免中文或者编译时开UNICODE第三FLAGS_logbufsecs 0并在退出前调google::ShutdownGoogleLogging()它会强制 flush。4.4CHECK宏在 Release 下行为异常现象Debug 下CHECK_EQ(a, b)正常 abortRelease 下直接跳过不检查。原因glog 的CHECK在NDEBUG下默认被禁用这是设计行为不是 bug。解决如果 Release 也要保留检查编译时定义GLOG_FORCE_CHECK或者用CHECK的替代宏。但更推荐的做法是CHECK只用于「不可能发生」的内部不变量Release 下确实该关对外部输入用LOG(FATAL)或显式判断。4.5 多线程下日志交错或丢失现象多线程同时写日志文件里出现半行或者某些线程的日志完全不见。原因glog 本身是线程安全的但FLAGS_logbufsecs缓冲 多线程 flush 时机不确定可能导致交错。另外如果多个线程各自调InitGoogleLogging会重复初始化。解决InitGoogleLogging只在main里调一次其他线程直接用LOG宏。如果对交错敏感设FLAGS_logbufsecs 0并接受性能损耗或者用google::SetLogDestination按线程分文件需要自己包一层。glog 不提供按线程分文件的内置支持这是它的边界。5. 进阶把 glog 日志接进 Windows 事件日志与自动化排查5.1 用SetLogDestination之外的方式接管输出glog 默认写文件但很多 Windows 项目要求日志同时进「事件查看器」或者走OutputDebugString给 VS 调试窗口。glog 没有直接提供回调接口但可以通过继承google::LogSink实现#include glog/logging.h #include windows.h class EventLogSink : public google::LogSink { public: void send(google::LogSeverity severity, const char* full_filename, const char* base_filename, int line, const struct tm* tm_time, const char* message, size_t message_len) override { // 转成宽字符写事件日志 std::wstring wmsg(message, message message_len); HANDLE h RegisterEventSourceW(nullptr, LMyApp); if (h) { LPCWSTR strs[1] { wmsg.c_str() }; ReportEventW(h, severity google::GLOG_ERROR ? EVENTLOG_ERROR_TYPE : EVENTLOG_INFORMATION_TYPE, 0, 0, nullptr, 1, 0, strs, nullptr); DeregisterEventSource(h); } } }; // 使用 EventLogSink sink; google::AddLogSink(sink); LOG(INFO) this also goes to Windows Event Log; // 退出前 google::RemoveLogSink(sink);LogSink的send会在每条日志时被调用注意它是在写日志的线程里同步执行的别在里面做耗时操作否则拖慢业务线程。ReportEventW需要管理员权限注册事件源普通用户跑会失败所以生产环境一般只在服务账户下用。5.2 验证日志是否真的落盘一个可复现的检查脚本写完日志代码怎么确认文件真的生成了、内容完整我习惯用一个 PowerShell 脚本做冒烟检查# check_glog.ps1 $logDir D:\logs\myapp $today Get-Date -Format yyyyMMdd # 找今天的 INFO 日志 $files Get-ChildItem -Path $logDir -Filter INFO_*$today* -Recurse if ($files.Count -eq 0) { Write-Error no INFO log found for today exit 1 } foreach ($f in $files) { $lines Get-Content $f.FullName Write-Host file: $($f.Name), lines: $($lines.Count) # 检查最后一行是否有时间戳 $last $lines[-1] if ($last -notmatch ^\d{4} \d{2}:\d{2}:\d{2}) { Write-Warning last line missing timestamp: $last } }这个脚本检查三件事文件存在、行数非零、最后一行格式正常。如果最后一行没有时间戳说明程序退出时没 flush回去检查ShutdownGoogleLogging有没有调。我一般把这个脚本挂到 CI 的冒烟测试里每次构建后跑一遍比人肉看日志靠谱。5.3 一个我踩过的坑路径长度超过 260 字符Windows 默认MAX_PATH是 260glog 生成的文件名本身不长但如果你的日志目录嵌套很深加上主机名、用户名、日期、进程号很容易超。超了之后CreateFile返回ERROR_PATH_NOT_FOUNDglog 静默失败日志消失。解决办法有两个一是把日志目录设在根目录附近比如D:\logs\app别放D:\projects\company\team\product\build\...二是编译时开长路径支持在 manifest 里加longPathAware并且注册表启用LongPathsEnabled。我现在的习惯是所有 Windows 项目的日志目录强制用D:\logs\appname从源头上避开路径长度问题。从那以后我每次接 glog 到新 Windows 工程都强制走一遍「编译配置对齐 → 初始化参数检查 → 冒烟脚本验证」这三步少一步后面都可能翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表