
1. 项目概述如果你在C项目里用过Google Loggingglog大概率对它又爱又恨。爱的是它那套基于C流式风格的日志API用起来确实顺手LOG(INFO) “Something happened: “ value;这种写法既直观又符合C程序员的直觉比printf或者sprintf安全也比一些复杂的日志库要轻量。恨的是当你兴致勃勃地把它集成到项目里编译、链接、运行时各种稀奇古怪的问题就冒出来了链接时找不到符号、运行时日志文件不生成、多线程下日志错乱、或者想自定义个输出格式却无从下手。更让人头大的是这个项目在2025年6月30日已经被官方归档Archived不再维护了。这意味着你遇到的新问题可能再也等不到官方的修复社区支持的力度也会逐渐减弱。但这并不意味着glog就完全不能用了。恰恰相反作为一个曾经被广泛使用、代码质量极高的库它在很多存量项目和特定场景下依然有很强的生命力。很多问题并非无解只是需要一些“踩坑”后总结出来的经验。这篇文章就是基于我过去几年在多个C服务端项目中深度使用glog的经验为你梳理出一份“避坑指南”和“解决方案手册”。我会覆盖从编译安装、基础配置、高级用法到疑难杂症排查的全过程所有方案都是亲测有效并且完全基于免费、开源的生态。无论你是刚刚接触glog的新手还是在维护一个使用了glog的老项目这篇文章都能帮你节省大量折腾的时间。2. 核心问题全景与解决思路拆解在深入具体问题之前我们有必要先对glog可能遇到的问题做一个全景式的扫描并理解其背后的根源。这能帮助你在遇到问题时快速定位方向而不是盲目搜索。2.1 问题分类与根源分析glog的问题大致可以归结为以下几类每一类都有其特定的发生场景和解决思路构建与集成问题这是新手遇到最多的第一道坎。问题通常表现为编译错误、链接错误如undefined reference togoogle::InitGoogleLogging。根源在于依赖缺失glog本身依赖不多但在某些系统如较老的Linux发行版上可能需要libunwind或gflagsGoogle的命令行标志库。虽然glog可以不依赖gflags但很多示例和旧文档默认你装了。构建系统不匹配你的项目可能用CMake、Bazel、Makefile或Autotools而glog的源码构建方式需要与之适配。直接#include源码或使用预编译的不匹配的库文件很容易出问题。C标准版本冲突glog要求C14如果你的项目用的是C11或更老的标准或者用的是C17/20的某些新特性可能在编译时遇到语法或标准库兼容性问题。运行时配置与行为问题库能编译链接但一运行就发现和预期不符。比如日志文件不输出程序运行了但当前目录下没有生成program name.hostname.user name.log.severity level.date.time.pid这样的文件。日志级别不生效设置了FLAGS_minloglevel但DEBUG或INFO日志依然满天飞。日志格式不符合预期想添加线程ID、自定义时间格式却发现无从下手。性能问题在高并发场景下日志输出成为性能瓶颈。 根源在于对glog的运行时初始化流程、标志位Flags系统以及内部缓冲机制不了解。高级特性与兼容性问题当你想用一些更强大的功能时会遇到障碍。自定义日志接收器Sink想把日志同时输出到文件、网络和标准错误需要深入glog的内部接口。信号处理与崩溃日志glog内置了信号处理能在程序崩溃时生成堆栈跟踪但这需要正确的编译选项和系统支持如libunwind。与其它日志库共存项目历史遗留部分模块用了log4cxx、spdlog等如何让glog与之和平共处官方归档后的替代方案既然官方不维护了是硬着头皮继续用还是迁移有哪些平滑迁移的选项2.2 总体解决思路面对这些问题我的解决思路遵循一个从基础到高级、从通用到特定的路径确保环境纯净与依赖正确这是所有工作的基础。使用包管理器如apt, yum, vcpkg, conan安装通常是避免编译问题的最快方法。理解初始化与标志位花半小时读懂InitGoogleLogging和几个核心标志位FLAGS_log_dir,FLAGS_minloglebug,FLAGS_stderrthreshold能解决80%的运行时问题。掌握日志流与宏的语义LOG(LEVEL),VLOG(n),DLOG,PLOG这些宏有什么区别什么时候用CHECK而不是assert理解这些能写出更健壮、信息量更足的日志。深入内部机制以定制化当标准功能不满足时需要研究LogMessage、LogSink等内部类这是解决高级需求的钥匙。建立监控与排查习惯将日志问题本身也纳入监控比如定期检查日志目录磁盘空间、设置日志文件滚动Rotate策略防患于未然。接下来我们就按照这个思路进入具体的实操环节。3. 构建、集成与环境配置的坑与解法让我们从第一步开始把glog稳稳地“请”进你的项目。3.1 选择正确的安装方式首选系统包管理器对于快速验证和大多数开发环境这是最推荐的方式。它能自动处理依赖和库路径。# Ubuntu/Debian sudo apt-get install libgoogle-glog-dev # CentOS/RHEL (需要EPEL仓库) sudo yum install epel-release sudo yum install google-glog-devel # macOS (使用Homebrew) brew install glog注意包管理器提供的版本可能不是最新的。如果遇到需要特定版本或补丁的情况才需要考虑从源码编译。次选从源码编译当包管理器版本不合适或者你需要开启某些特定编译选项如调试符号、定制化的安装路径时需要源码编译。# 1. 克隆代码 (注意主仓库已归档可考虑fork或使用社区维护的镜像) git clone https://github.com/google/glog.git cd glog # 2. 创建构建目录并配置 mkdir build cd build # 关键配置项 # -DWITH_GFLAGSOFF # 如果不需gflags支持建议关闭减少依赖 # -DWITH_UNWINDON # 如果需要更好的崩溃堆栈信息开启需系统有libunwind # -DBUILD_SHARED_LIBSON # 默认静态库如需动态库则开启 cmake -DCMAKE_BUILD_TYPERelease -DWITH_GFLAGSOFF .. # 3. 编译并安装 make -j$(nproc) sudo make install # 默认安装到 /usr/local避坑点1安装路径冲突如果你系统里已经通过包管理器安装了glog再从源码make install到/usr/local可能会造成头文件和库文件版本混乱。一个干净的做法是使用CMAKE_INSTALL_PREFIX指定一个独立路径并在你的项目中显式引用这个路径。cmake -DCMAKE_BUILD_TYPERelease -DCMAKE_INSTALL_PREFIX/opt/myglog .. make -j$(nproc) sudo make install之后在你的项目CMakeLists.txt中可以通过find_package(glog REQUIRED PATHS /opt/myglog)来定位。避坑点2Windows下的编译Windows下编译glog相对麻烦缺少一些Unix特有的头文件和功能。最省事的方法是使用vcpkg。# 安装vcpkg后 vcpkg install glog:x64-windows然后在你的CMake项目中配置-DCMAKE_TOOLCHAIN_FILE[path/to/vcpkg]/scripts/buildsystems/vcpkg.cmake即可。3.2 项目集成CMake最佳实践假设你的项目使用CMake集成glog的最佳实践如下cmake_minimum_required(VERSION 3.10) project(MyGlogProject) # 方式1使用find_package (推荐清晰) find_package(glog REQUIRED CONFIG) # 如果是从源码安装到标准路径或vcpkg # 或者指定路径 # find_package(glog REQUIRED PATHS /opt/myglog/lib/cmake/glog) add_executable(my_app main.cpp) target_link_libraries(my_app PRIVATE glog::glog) # 方式2使用pkg-config (如果glog提供了.pc文件) find_package(PkgConfig REQUIRED) pkg_check_modules(GLOG REQUIRED libglog) target_include_directories(my_app PRIVATE ${GLOG_INCLUDE_DIRS}) target_link_libraries(my_app PRIVATE ${GLOG_LIBRARIES})关键点使用glog::glog这个CMake导入目标Imported Target它能自动传递所有必要的编译定义和依赖关系比手动指定include_directories和link_libraries更可靠。3.3 一个可运行的“Hello Log”示例创建一个main.cpp文件验证集成是否成功#include glog/logging.h #include iostream int main(int argc, char* argv[]) { // 初始化glog。argv[0]用于生成默认日志文件名。 google::InitGoogleLogging(argv[0]); // 设置日志输出到标准错误除了ERROR和FATAL它们默认就输出 // 这行很重要否则你可能在控制台看不到任何输出 google::InstallFailureSignalHandler(); // 可选安装失败信号处理器用于捕获崩溃 LOG(INFO) This is an informational message.; LOG(WARNING) This is a warning message.; LOG(ERROR) This is an error message.; // DLOG(INFO) This debug log will only be printed in debug builds.; // 使用VLOG进行分级详细日志记录级别由FLAGS_v控制 VLOG(1) Im printed when you run the program with --v1 or higher; VLOG(2) Im printed when you run the program with --v2 or higher; // 使用CHECK宏进行断言比assert更强大失败会记录FATAL日志 int x 10; CHECK_GT(x, 0) x must be positive; // 检查 x 0 std::cout Program finished. Check the log files in the current directory. std::endl; // 关闭日志库非必须程序退出时会自动清理 google::ShutdownGoogleLogging(); return 0; }编译并运行g -stdc14 -o my_app main.cpp -lglog -lpthread ./my_app运行后你应该能在当前目录看到生成的日志文件如my_app.yourhostname.yourusername.log.INFO.20250315-123456.7890并在控制台看到ERROR和FATAL级别的日志因为默认FLAGS_stderrthreshold2即ERROR及以上级别输出到stderr。如果这一步成功了恭喜你最基础的集成已经完成。如果失败了请根据错误信息回溯到前面的安装和集成步骤。4. 运行时配置让日志听话的关键glog的行为几乎完全由一系列gflags管理的命令行标志控制。即使你不链接gflags库glog内部也有一套兼容的实现来解析这些标志。理解并正确设置它们是驯服glog的关键。4.1 核心标志位详解与设置方法你可以在代码中直接设置这些标志也可以通过命令行参数传递。在代码中设置程序启动时int main(int argc, char* argv[]) { // 必须在InitGoogleLogging之前设置一些标志 FLAGS_logtostderr false; // 默认false。为true时所有日志输出到stderr不生成日志文件。 FLAGS_alsologtostderr false; // 默认false。为true时日志除了写入文件也同时输出到stderr。 FLAGS_stderrthreshold google::GLOG_ERROR; // 默认2(ERROR)。设置输出到stderr的最低级别。0(INFO), 1(WARNING), 2(ERROR), 3(FATAL) FLAGS_log_dir /var/log/myapp; // 默认空当前目录。日志文件输出目录。务必确保进程有写权限 FLAGS_minloglevel google::GLOG_INFO; // 默认0(INFO)。全局最低日志级别低于此级别的日志将被直接丢弃。 FLAGS_v 0; // 默认0。VLOG的全局级别只有VLOG(n)中 n FLAGS_v 的日志才会被记录。 google::InitGoogleLogging(argv[0]); // ... 其余代码 }通过命令行参数设置更灵活# 将日志同时输出到文件和stderr ./my_app --alsologtostderr # 只输出到stderr不生成文件 (常用于调试) ./my_app --logtostderr # 设置stderr只输出ERROR及以上 ./my_app --stderrthreshold2 # 设置日志目录 ./my_app --log_dir/tmp/my_logs # 只记录WARNING及以上级别的日志 ./my_app --minloglevel1 # 启用VLOG级别为1的详细日志 ./my_app --v1实操心得初始化顺序的坑FLAGS_log_dir、FLAGS_logtostderr等影响输出目标的标志必须在google::InitGoogleLogging之前设置否则初始化可能不会采用你的设置。而像FLAGS_v这种控制日志内容的标志在初始化前后设置都可以。一个好习惯是在main函数开头紧接在gflags解析如果有的话之后InitGoogleLogging之前集中设置所有输出相关的标志。4.2 日志文件命名与滚动策略默认的日志文件名很长包含了程序名、主机名、用户名、级别、时间戳和PID这保证了唯一性但也可能让人眼花缭乱。你可以通过设置FLAGS_log_file_name来影响基础命名但不建议完全自定义因为唯一性可能被破坏。更重要的管理在于日志文件滚动Rotate防止单个文件过大。// 在代码中设置滚动策略 FLAGS_max_log_size 100; // 单位MB默认1800MB。单个日志文件达到此大小后会滚动到下一个文件。 FLAGS_stop_logging_if_full_disk true; // 默认false。当磁盘空间不足时停止日志记录避免写满磁盘。glog的滚动是自动的。当myapp.xxx.log.INFO.20250315-120000.12345达到max_log_size后会被重命名为myapp.xxx.log.INFO.20250315-120000.12345.1然后创建一个新的myapp.xxx.log.INFO.20250315-120001.12345继续写入。旧文件会以.1,.2后缀依次递增。避坑点日志文件清理glog不会自动删除旧的日志文件这是一个潜在的磁盘空间杀手。你必须自己实现清理逻辑例如通过crontab定时任务# 每天凌晨2点删除超过7天的glog日志文件 0 2 * * * find /path/to/log/dir -name \*.log.*\ -mtime 7 -delete或者在你的应用程序启动或关闭时调用一个清理函数。千万不要指望glog帮你做这件事。4.3 多线程与异步日志性能考量glog的日志输出默认是线程安全的但它是同步的。这意味着每次调用LOG(INFO) ...当前线程都会等待I/O操作写文件或写stderr完成。在高并发、高频率打日志的场景下这可能会成为性能瓶颈。glog本身不提供内置的异步日志机制。社区有一些方案比如将日志先写入一个内存队列再由后台线程批量写出但这需要你自行实现或集成第三方库如spdlog的异步模式。一个简单的性能优化技巧对于极度频繁的调试日志例如在紧凑循环中使用VLOG并默认将FLAGS_v设为0来禁用。在需要排查问题时再通过命令行动态开启--v2。这比用LOG(DEBUG)配合FLAGS_minloglevel更灵活因为VLOG的调用开销在禁用时极低。5. 高级用法与定制化技巧当基础功能满足不了你时就需要深入了解glog的内部接口了。5.1 自定义日志格式glog默认的日志格式是固定的[级别][月/日 时:分:秒.微秒][线程ID?] 文件名:行号] 消息体。有时你可能想添加更多上下文比如协程ID、请求ID或者改变时间格式。这需要通过自定义LogMessage的生成逻辑来实现比较底层。一个更实用的方法是继承google::LogSink。LogSink允许你拦截每一条日志消息然后以任意格式输出到任意地方。#include glog/logging.h #include iomanip #include sstream class MyCustomSink : public google::LogSink { public: void send(google::LogSeverity severity, const char* full_filename, const char* base_filename, int line, const google::LogMessageTime logmsgtime, const char* message, size_t message_len) override { // 忽略某些级别的日志 if (severity google::GLOG_WARNING) { return; } std::ostringstream oss; // 自定义格式: [时间][级别][文件:行] 消息 auto t logmsgtime.time(); std::time_t tt std::chrono::system_clock::to_time_t(t); oss [ std::put_time(std::localtime(tt), %Y-%m-%d %H:%M:%S) ][ google::GetLogSeverityName(severity) ] [ base_filename : line ] std::string(message, message_len) std::endl; // 输出到标准错误也可以写到网络、数据库等 std::cerr oss.str(); // 同时你也可以继续让默认的文件日志生效如果你还添加了默认的file sink } }; int main(int argc, char* argv[]) { google::InitGoogleLogging(argv[0]); MyCustomSink sink; google::AddLogSink(sink); // 注册自定义Sink LOG(INFO) This info will be filtered out by our sink.; LOG(WARNING) This warning will be formatted by our sink.; LOG(ERROR) This error will also be formatted by our sink.; // 注意sink对象必须在程序生命周期内有效所以通常定义为全局或静态变量。 // 程序结束前可以调用 google::RemoveLogSink(sink); 但非必须。 google::ShutdownGoogleLogging(); return 0; }5.2 条件日志与调试宏glog提供了一系列强大的宏用于有条件的日志记录和检查这比简单的if语句加LOG更高效、更安全。LOG_IF(LEVEL, CONDITION): 当条件满足时才记录日志。int error_count 5; LOG_IF(ERROR, error_count 0) Encountered error_count errors.;LOG_EVERY_N(LEVEL, N): 每执行到第N次时记录一次日志。非常适合记录周期性事件避免日志刷屏。for (int i 0; i 10000; i) { LOG_EVERY_N(INFO, 1000) Processed google::COUNTER items.; // COUNTER是内部计数器 // ... 处理逻辑 }LOG_FIRST_N(LEVEL, N): 前N次执行时记录日志。DLOG(LEVEL),DLOG_IF,DVLOG: 这些是调试版本专用的宏在非调试NDEBUG定义构建中会被完全编译掉不产生任何运行时开销。强烈推荐在调试日志中使用。CHECK系列宏这是glog的王牌功能之一。它不仅是断言失败时会生成FATAL级别日志打印堆栈跟踪如果编译时支持并终止程序。比标准assert更强大。CHECK_NOTNULL(pointer); // 检查指针非空 CHECK_EQ(a, b); // 检查相等 CHECK_GE(a, b); // 检查 a b CHECK_STREQ(str1, str2); // 检查C风格字符串相等 // 所有CHECK宏都支持 流操作符附加额外信息 CHECK_LT(index, vector.size()) Index out of bounds. index index , size vector.size();实操心得善用DLOG和VLOG将开发调试用的日志全部用DLOG和VLOG来写。DLOG在发布版本中自动消失零开销。VLOG可以通过命令行动态调整级别在线上排查问题时非常有用无需重新编译部署。而LOG(INFO)/LOG(WARNING)等则用于记录程序正常运行时的关键状态和事件。5.3 信号处理与崩溃堆栈google::InstallFailureSignalHandler();这行代码非常有用。它会让glog捕获SIGSEGV段错误、SIGILL非法指令等致命信号并在程序崩溃前将堆栈跟踪信息输出到stderr和日志文件。这对于定位线上崩溃原因至关重要。要使堆栈跟踪信息更可读包含函数名和行号而不是一堆地址你需要编译你的程序时加上-g或-rdynamic选项GCC/Clang以包含调试符号或动态符号表。确保系统安装了libunwind或glibc的backtrace相关功能并且glog在编译时开启了WITH_UNWIND或WITH_STACKTRACE支持。6. 疑难杂症排查实录即使按照最佳实践操作一些奇怪的问题还是会出现。下面是我遇到过的典型问题及其解决方法。6.1 常见问题速查表问题现象可能原因解决方案编译错误undefined reference to ‘google::InitGoogleLogging’1. 没有链接glog库 (-lglog)。2. 链接顺序不对-lglog需要放在源文件/目标文件之后。3. 使用了C编译器链接C库罕见。1. 确保编译命令包含-lglogCMake中使用target_link_libraries。2. 调整链接顺序g main.cpp -o app -lglog -lpthread。3. 如果是C项目使用extern “C”包含头文件并链接glog。运行时无日志文件生成1.FLAGS_log_dir路径不存在或进程无写权限。2.FLAGS_logtostderr或FLAGS_alsologtostderr被设为true。3. 当前目录不可写。1. 检查目录权限或在代码中创建目录mkdir(FLAGS_log_dir.c_str(), 0755)。2. 确认这些标志在InitGoogleLogging前被正确设置为false。3. 使用--log_dir指定一个绝对路径。日志同时输出到屏幕和文件但只想保留一种FLAGS_alsologtostderr被设置为true。如果想只输出到文件确保FLAGS_logtostderrfalse且FLAGS_alsologtostderrfalse。如果想只输出到屏幕设置FLAGS_logtostderrtrue。CHECK宏失败时没有堆栈信息1. 程序编译时未包含调试符号 (-g)。2. 编译时被优化如-O2可能导致行号信息不准确。3. glog编译时未启用栈回溯支持。1. 开发/调试版本编译时加上-g选项。2. 对于发布版本考虑使用-rdynamic并保留一份带调试符号的二进制用于调试。3. 从源码编译glog时确保-DWITH_UNWINDON。多线程程序日志内容错乱或丢失虽然glog内部是线程安全的但极端高并发下如果多个线程同时写一条很长的日志多次操作可能会交叉。对于单条日志确保在一条LOG语句内完成构建。避免先构建字符串再输出因为中间状态可能被其他线程打断。LOG(INFO) “Key: “ key “, Value: “ value;是原子的。程序退出时报告内存泄漏google::LogMessage::~LogMessage通常是因为没有调用google::ShutdownGoogleLogging()或者日志线程还未完全结束程序就退出了。1. 在main函数返回前调用google::ShutdownGoogleLogging()。2. 确保所有全局/静态对象的析构函数中不再打日志。3. 对于某些检测工具如Valgrind这可能是误报可以忽略glog内部的内存池。日志文件体积增长过快1.FLAGS_max_log_size设置过大。2. 日志级别太低如INFO打印了过多无关紧要的日志。3. 没有清理旧日志文件的策略。1. 根据需求调整max_log_size如100MB。2. 提高FLAGS_minloglevel到WARNING或ERROR减少日志量。3.必须实现外部日志清理如crontab脚本。在动态库中使用glog主程序也使用冲突符号冲突或重复初始化。理想情况下整个进程应该只使用一份glog。如果动态库是第三方库你无法控制可以考虑1. 让动态库静态链接glog并隐藏符号。2. 与库提供者协商使用统一的glog版本。这是一个复杂问题可能需具体分析。6.2 深入排查使用GDB调试glog问题当遇到一些玄学问题比如日志偶尔丢失、程序在打日志时卡住可能需要更深入的调试。查看glog内部状态glog提供了一些未公开但有用的函数。例如你可以通过google::GetLoggingDirectories()来检查它当前认为的日志目录是什么。GDB断点如果你怀疑问题在glog内部可以编译一份带调试符号的glog然后在关键函数上设断点。gdb your_program (gdb) break google::LogMessage::SendToLog (gdb) break google::LogFileObject::Write这可以帮助你观察每条日志是如何被处理和写入的。跟踪系统调用使用strace查看写文件是否失败。strace -e tracefile -o strace.out ./your_program然后查看strace.out文件中对日志文件的open、write、close等调用是否返回错误如-1 EACCES权限错误。6.3 官方归档后的出路与迁移考量最后我们无法回避glog已被归档的事实。对于新项目我建议慎重考虑。对于老项目你有几个选择继续使用自行维护glog代码很稳定如果你的使用场景简单且当前版本工作良好可以继续使用。将源码作为第三方库引入你的项目并做好不再有更新的心理准备。关注社区是否有重要的安全补丁分支。迁移到absl::Log(Abseil Logging)这是Google官方推荐的继任者属于Abseil库的一部分。它设计更现代性能可能更好并且与Google内部的C生态集成更紧密。但API与glog不完全相同迁移需要一定工作量。迁移到ng-log这是一个社区维护的、旨在API兼容glog的项目。目标是让现有使用glog的代码只需极少的修改就能切换过去。如果你的项目严重依赖glog的特定API这是一个平滑迁移的选项。评估其他日志库如spdlog高性能特性丰富头文件库、fmtlog基于fmt库高性能、Boost.Log功能强大但较重。这需要重新评估需求和进行大量代码修改。迁移建议如果项目处于活跃开发期且对日志功能有更高要求如异步、更灵活的格式、sink趁早规划迁移到spdlog或absl::Log。如果项目稳定且glog完全满足需求可以暂时不动但要在构建系统中固定glog的版本和源码避免未来因系统环境变化导致的不兼容。说到底工具是为人服务的。glog虽然“年事已高”但其简洁的设计和稳定的表现在无数C项目中证明了它的价值。希望这份汇集了多年踩坑经验的指南能让你在使用glog的路上走得更顺畅一些。