【C++三方组件】glog:Google 出品的 C++ 日志库

【C++三方组件】glog:Google 出品的 C++ 日志库

【摘要】:spdlog 之前十年的世界:LOG(INFO) << "msg";------流式语法、I/W/E 级别前缀、文件行号自动附体。glog 三件套是 LOG 分级、VLOG 按号码细控、CHECK 断言家族(实测失败即 F 级报文 Check failed: retries <= 10 (99 vs. 10) + 堆栈 + 非零退出)。Why 拆三问:流式语法为什么是那一代的选择、VLOG 的号码比级别好在哪、CHECK 为什么比 assert 更适合生产。与 gflags 的黄金搭档收尾。

【版本基准】:glog 0.7.1(BSD-3)|C++17|文中输出均为 g++ 13.1 实测

(一句话定位:glog 是「C++ 日志词汇表的立法者」------哪怕你最终用 spdlog,说的仍是它定的词。)

1. What:上一代的日志标准

glog 从 Google 内部走出(2010s 开源),设计带着鲜明的时代与工程烙印:日志是宏、级别是大写字母、断言是一等公民 。它不追接口优雅,追的是「百万行代码库里每个 LOG 都便宜、每条日志自带源位置、每个不变量都能一行声明」。

今天的新项目默认会选 spdlog(下一篇),但 glog 仍是理解存量代码与 Google 系生态(gflags、abseil logging)的必修课。

源码集成时与 gflags 的组合值得单独说:-DWITH_GFLAGS=ON 编出的 glog 会把全部 FLAGS_ 注册成 gflags 标志------日志参数从此走 gflags 的收割与 help 体系 (本篇实测为独立构建,FLAGS_ 直接可用,两种姿势的 FLAGS_ 行为一致、来源不同)。混合栈的团队常选组合构建,让 --help 一屏看全。

(接入姿势与本篇实测对齐:CMake target 链接、main 首行 Init、FLAGS_ 直调。)

2. 项目接入

json 复制代码
// vcpkg:vcpkg.json
{ "dependencies": [ "glog" ] }
cmake 复制代码
find_package(glog CONFIG REQUIRED)
target_link_libraries(app PRIVATE glog::glog)

编译库(本篇实测即本地构建);注意用 CMake target 接入------手工链静态库需要正确的导出宏配置(实测直连报「not included correctly」)。

输出格式的字段逐个点评(实测行 I20260918 01:30:24.192250 1 demo_glog.cpp:6]):首字母级别(I/W/E/F)------grep 一发命中;微秒时间戳------对齐多机日志的时钟轴;线程号(第 4 列的 1)------并发归因;文件:行 ------直达现场;] 后正文。一行 60 字节回答了「何时何线程哪个分支说了什么」------grep 友好是格式设计的第一目标,这个审美统治了服务端日志二十年。

3. 核心概念:宏 + 流 + 自动源位置

cpp 复制代码
google::InitGoogleLogging(argv[0]);  // 必须最先调用
FLAGS_logtostderr = 1;               // 直写 stderr(开发常态)

LOG(INFO) << "info msg " << 42;
LOG(WARNING) << "warn msg";
LOG(ERROR) << "error msg";

三个特征一眼可辨:流式语法<< 拼接,那一代的惯用法);每行自带 文件:行号 (宏展开时抓取,零成本);级别是前缀字母(I/W/E/F)。实测输出:

text 复制代码
I20260918 01:30:24.192250 1 demo_glog.cpp:6] info msg 42
W20260918 01:30:24.193006 1 demo_glog.cpp:7] warn msg
E20260918 01:30:24.193008 1 demo_glog.cpp:8] error msg

常用 FLAGS 速查(与 gflags 联动,可命令行直调):logtostderr(开发期直打屏)、v(VLOG 号码阈值)、log_dir(文件模式输出目录)、alsologtostderr(文件 + 屏双路)、minloglevel(全局静音,0 全开 3 只 fatal)、logbufsecs(缓冲刷新间隔)。运维排障的「三板斧」就是 --v=2 --logtostderr=1 --log_dir=/tmp------三个标志不用改代码。

CHECK 家族的完整清单按用途分三组:等式比较CHECK_EQ/NE/LT/LE/GT/GE,两侧值打进报文------实测的 (99 vs. 10));状态断言CHECK(ptr)CHECK_NOTNULL(ptr)------顺带返回指针可直接用在表达式里);前置与不变量DCHECK_* 系列是 debug-only 版本------release 蒸发,给「昂贵的断言」留的口子,与 assert 的 NDEBUG 语义对齐但命名显式)。选型口诀:不变量用 CHECK(永不蒸发)、热路径昂贵检查用 DCHECK

(实测输出三例:I/W/E 分级行、VLOG(1) 随 FLAGS_v 显隐、CHECK_LE 失败的 F 级报文与非零退出。)

4. How:VLOG 细控与 CHECK 断言(实测)

VLOG------比级别更细的开关 。级别只有四档,而「这个模块想多打一点」的需求是连续的------glog 的答案是用号码VLOG(1)VLOG(2)......默认全关,FLAGS_v = N 打开所有号码 ≤ N 的:

cpp 复制代码
FLAGS_v = 1;
VLOG(1) << "vlog1 now visible";
VLOG(2) << "vlog2 still hidden";
// 实测:第 1 行输出,第 2 行沉默

生产常年关着,排障时按号逐级打开------日志的「音量旋钮」从四档变成了无级

CHECK------生产级断言 。assert 的问题:NDEBUG 一开就蒸发。CHECK 家族(CHECK_EQ/LE/...)永远在,失败时打 F 级日志、附堆栈、然后退出:

cpp 复制代码
int retries = 99;
CHECK_LE(retries, 10) << "retries out of range: " << retries;

实测(单独进程运行):

text 复制代码
F20260918 01:30:39.313477 1 demo_glog_check.cpp:6]
  Check failed: retries <= 10 (99 vs. 10)
  retries out of range: 99
    @   00007ff783f14c3a      ← 附调用栈地址
(进程非零退出)

Check failed: retries <= 10 (99 vs. 10)------表达式、两侧实值 都在,配 << 的补充说明------一条报错就是一份现场报告。

时间线视角补一段:glog 之后 Google 内部演化出了 abseil 的 logging( absl::Log 家族),glog 本身进入维护态(本篇实测 0.7.1 仍在修)------但它确立的词汇表 (VLOG 的号码、CHECK 家族、文件名行号前缀)已成为 C++ 日志的通用语,后来的库(包括 spdlog 的级别讨论、absl 的 LOG 接口)都在这套词汇上说话。学 glog 的最大回报不是用它,是读懂这套词汇

VLOG 的「按模块」细控展开(文档级):--vmodule=sync=3,db=1 让 sync 文件的 VLOG 开到 3、db 开到 1------模块名的匹配规则是文件名(不含扩展名),粒度恰好落在「一个 .cpp 一个关注点」的工程直觉上。配合 fatal 时的自动堆栈(实测 CHECK 死亡现场自带地址栈),「多打点不罚、按需开灯」的调试体验完整成型------这套体验是后来者 spdlog 也要用 per-logger level 补课的原因。

5. Why:三个追问

① 流式语法为什么是那一代的选择? 因为 2010 年前后可变参数模板尚未普及(C++11 才落地),「类型安全地拼任意内容」的答案只有 operator<<;C 风格 printf 类型不安全、fmt 式 {} 占位要到 2015+ 才有库、2020 才进标准。glog 的 << 是那个年代的类型安全上限------今天看它冗长,是语言演进史叠在 API 上的地层(与第 4 篇 fmt 摘要里「手动挡」的对照:不是设计者不想优雅,是当时没有这套语法)。

② VLOG 的号码比级别好在哪? 好在多维度 :级别回答「这条消息多严重」,号码回答「这个模块想多说几句」------两个正交的旋钮不该挤在同一根轴上。--vmodule=sync=3,db=1(按模块设号)把细控推到模块粒度。spdlog 后来也吸收了这个思想(per-logger level),但「号码 + 模块名」的组合仍是 glog 最锋利的地方。

③ CHECK 为什么适合生产? 三个设计决定:NDEBUG 不影响 (与 assert 的本质区别------不变量不该随编译配置蒸发);失败即退出 (坏不变量下继续跑是更大的事故,与第 17 篇 gflags 校验器同哲学:错误离定义点越近越好);先报告再死(F 级日志 + 堆栈,尸体自带遗书)。测试代码里它配合 gtest 的死亡断言食用更佳(〔关联 第 21 篇〕)。

文件模式的默认行为是「写到临时文件、达到大小切档、保留 N 个」------新手常见「明明没 logtostderr 却哪都没输出」其实是写进了 *.log.INFO. 系列文件(当前目录或 log_dir)------先找文件再怀疑配置 。 severity 链也值得知道:ERROR 级默认同时写 INFO/WARNING/ERROR 三个文件(高层级文件是低层级的超集)------排查时看最专门的那个文件。

「先初始化后使用」的时间窗也有讲究:全局对象在 InitGoogleLogging 之前打日志的行为未定义(常见是丢或崩)------让含 LOG 的全局对象滚出全局作用域是 glog 工程的一号规范;实在需要早期日志,把 init 提到 main 第一行(本篇实测姿势)或用局部静态延迟。

6. 坑与最佳实践(实测依据)

  1. 必须 InitGoogleLogging 先行,且 argv0 会成为日志文件名前缀------改名从源头改。
  2. 默认写文件不打屏 (与 spdlog 相反):开发期先 FLAGS_logtostderr = 1,否则新手会以为「日志没生效」。
  3. 手工链静态库要过导出宏 :直连 .anot included correctly(实测);用 CMake target 一了百了。
  4. ERROR 级别在部分版本有额外动作(如刷栈、计数),热路径慎用------它是给「要出事了」的,不是「值得注意」。
  5. 与 gflags 是天作之合FLAGS_logtostderrFLAGS_v 本身就是 gflags 标志------第 17 篇的全局标志模型在这里发光:命令行 --v=2 即时调音量。

共存叙事收尾:两者常在同一个进程里出现(新模块 spdlog、存量 glog)------文件各写各的没有冲突,但级别词汇要先对齐(glog 的 ERROR 对应 spdlog 的 err 还是 warn,团队要有一张对照表),否则跨文件的日志检索会被级别落差撕裂。迁移的第一步不是换库,是统一词汇。

(决策再压缩一句:新代码 spdlog、存量与 CHECK 重度 glog、两者共存先统一级别词汇表。)

7. 选型对比:与 spdlog 的世代差

glog spdlog
语法 LOG(X) << 流式 info("{}", x) fmt 式
细控 VLOG 号码 + 模块名 per-logger 级别
落盘 内建策略(偏服务端习惯) sink 组合更灵活
断言 CHECK 家族 ❌(非日志职责)
定位 Google 系/存量/要 CHECK 新项目默认

新项目选 spdlog、要 CHECK 自己加 assert 库;存量与 Google 生态选 glog------两个都懂,迁移时才不慌。

一个冷知识收尾:glog 的 LogMessage 死亡堆栈(实测 CHECK 失败时那串地址)在符号化后能精确定位到断言行------生产环境里「CHECK 崩了但没人知道哪行」的迷案,解法就是拿地址对符号文件离线解析(第 20 篇的技能)。glog 与 backward-cpp 在「死也要留下遗言」这件事上是同一条战壕

8. 延伸与联动

  • 官方 github.com/google/glog------README 的 flag 清单是运维调参速查表;
  • 第 17 篇 gflags(全局标志模型)、本篇(上一代标准)与下一篇 spdlog(现代答案)构成日志三视角;
  • 下一篇 backward-cpp:日志管「活着的时候」,崩溃后的现场交给谁。〔关联 第 20 篇〕

参考google/glog 0.7.1(BSD-3)。文中 I/W/E/VLOG 开关、CHECK 失败报文与非零退出均为本机实测(g++ 13.1,CMake target 接入)。

相关推荐
Lazionr2 小时前
二叉搜索树:从树形结构到高效查找
开发语言·c++
此生决int2 小时前
深入理解C++系列(21)——异常
开发语言·c++
曼巴UE52 小时前
UE5 客户端 需要的网络同步概念总结(3 )-事件同步 RPC 以及三种调用方式
网络·c++·网络协议·学习·rpc·ue5
All for pursuit.2 小时前
【栈-5】84.柱状图中最大的矩形
数据结构·c++·算法·leetcode
01二进制代码漫游日记2 小时前
C++之类和对象(上)02
c++
渡我白衣3 小时前
HttpRequest与HttpResponse的实现
服务器·数据结构·c++·人工智能·tcp/ip·机器学习·caffe
刃神太酷啦3 小时前
前端入门第一课:HTML 基础语法 + 常用标签 + 实战全解
服务器·c语言·前端·javascript·css·c++·html
蒸蒸yyyyzwd3 小时前
八股学习笔记 day42
c++·八股
凤年徐3 小时前
C++ 仿 muduo 高并发服务器:用户态缓冲区 Buffer 模块实现
linux·c++