文章目录
如何使用Google日志库(glog)
https://rpg.ifi.uzh.ch/docs/glog.html
译者:kimi.ai
介绍
Google glog 是一个实现应用级别日志记录的库。这个库提供了基于C++风格流的日志API和各种辅助宏。你可以通过简单地将内容流式传输到LOG(特定严重级别)来记录一条消息,例如:
#include <glog/logging.h>
int main(int argc, char* argv[]) {
// 初始化Google的日志库。
google::InitGoogleLogging(argv[0]);
// ...
LOG(INFO) << "Found " << num_cookies << " cookies";
}
Google glog定义了一系列宏,简化了许多常见的日志记录任务。你可以:
- 按严重级别记录消息,从命令行控制日志行为,
- 基于条件记录日志,当预期条件未满足时中止程序,
- 引入你自己的详细日志级别,等等。
本文档描述了glog支持的功能。请注意,本文档没有描述此库中的所有特性,而只是最有用的一部分。如果你想找到不太常见的特性,请检查src/glog
目录下的头文件。
严重级别
你可以指定以下严重级别之一(按严重程度递增的顺序):INFO
、WARNING
、ERROR
和FATAL
。记录一个FATAL
消息会终止程序(在消息记录之后)。
请注意,给定严重级别的消息不仅记录在该严重级别的日志文件中,还记录在所有较低严重级别的日志文件中。例如,严重级别为FATAL
的消息将记录在严重级别为FATAL
、ERROR
、WARNING
和INFO
的日志文件中。
DFATAL
严重级别在调试模式下(即没有定义NDEBUG
宏)记录一个FATAL
错误,但在生产中通过自动将严重级别降低到ERROR
来避免停止程序。
除非另有说明,glog将日志写入到文件名"/tmp/<program name>.<hostname>.<user name>.log.<severity level>.<date>.<time>.<pid>" (e.g., "/tmp/hello_world.example.com.hamaji.log.INFO.20080709-222411.10474")
。
默认情况下,glog将严重级别为ERROR
或FATAL
的日志消息复制到标准错误(stderr)以及日志文件中。
设置标志
有几个标志影响glog的输出行为。如果你的机器上安装了Google gflags库,configure
脚本(有关此脚本的详细信息,请参见包中的INSTALL文件)将自动检测并使用它,允许你在命令行上传递标志。例如,如果你想打开标志--logtostderr
,你可以使用以下命令行启动你的应用程序:
./your_application --logtostderr=1
如果Google gflags库没有安装,你可以通过环境变量设置标志,将标志名称前缀为"GLOG_",例如:
GLOG_logtostderr=1 ./your_application
以下标志最常用:
logtostderr
(布尔值,默认为false
)- 将日志消息记录到stderr而不是日志文件。
stderrthreshold
(整数,默认为2,即ERROR
)- 除了日志文件之外,还将此级别或更高级别的日志消息复制到stderr。
minloglevel
(整数,默认为0,即INFO
)- 记录此级别或更高级别的日志消息。
log_dir
(字符串,默认为空"")- 如果指定,日志文件将写入此目录,而不是默认的日志目录。
v
(整数,默认为0)- 显示所有
VLOG(m)
消息,其中m
小于或等于此标志的值。可被--vmodule
覆盖。有关详细信息,请参见关于详细日志记录的部分。
- 显示所有
vmodule
(字符串,默认为空"")- 每个模块的详细日志级别。参数必须包含一个逗号分隔的<模块名称>=<日志级别>列表。<模块名称>是一个全局模式(例如,
gfs*
表示所有名称以"gfs"开头的模块),与文件名基础(即忽略.cc/.h./-inl.h的名称)匹配。<日志级别>
会覆盖--v
给出的任何值。另请参见关于详细日志记录的部分。
- 每个模块的详细日志级别。参数必须包含一个逗号分隔的<模块名称>=<日志级别>列表。<模块名称>是一个全局模式(例如,
- 还有其他在logging.cc中定义的标志。请使用
grep
搜索源代码中的"DEFINE_"以查看所有标志的完整列表。
你也可以通过修改全局变量FLAGS_*
在你的程序中修改标志值。大多数设置在更新FLAGS_*
后立即生效。例外的是与目标文件相关的标志。例如,你可能想在调用google::InitGoogleLogging
之前设置FLAGS_log_dir
。这里是一个例子:
LOG(INFO) << "file";
// 大多数标志在更新值后立即生效。
FLAGS_logtostderr = 1;
LOG(INFO) << "stderr";
FLAGS_logtostderr = 0;
// 这不会改变日志目的地。如果你想设置这个值,
// 你应该在调用google::InitGoogleLogging之前做这个。
FLAGS_log_dir = "/some/log/directory";
LOG(INFO) << "the same file";
条件/偶尔记录
-
有时,你可能只想在某些条件下记录一条消息。你可以使用以下宏执行条件记录:
LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
"Got lots of cookies"消息只有在变量
num_cookies
超过10时才会记录。如果一行代码执行多次,可能有助于只在某些间隔记录一条消息。这种记录对于信息性消息最有用。 -
LOG_EVERY_N(INFO, 10) << "Got the " << google::COUNTER << "th cookie";
上面的代码在执行的第1次、第11次、第21次…时输出日志消息。请注意,特殊的
google::COUNTER
值用于标识正在发生的重复。 -
你可以使用以下宏将条件和偶尔记录结合起来。
LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Got the " << google::COUNTER << "th big cookie";
-
你可以使用以下宏限制输出到前n次发生的情况,而不是每nth次输出一条消息:
LOG_FIRST_N(INFO, 20) << "Got the " << google::COUNTER << "th cookie";
输出日志消息,直到执行的前20次。再次,
google::COUNTER
标识符指示正在发生的重复。
调试模式支持
特殊的"调试模式"日志宏只在调试模式下有效,并且在非调试模式编译时被编译为无。使用这些宏可以避免由于过度记录而减慢你的生产应用程序。
DLOG(INFO) << "Found cookies";
DLOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
DLOG_EVERY_N(INFO, 10) << "Got the " << google::COUNTER << "th cookie";
CHECK宏
在程序中频繁检查预期条件是一个很好的实践,以便尽早发现错误。CHECK
宏提供了在条件不满足时终止应用程序的能力,类似于标准C库中定义的assert
宏。
CHECK
宏在条件不为真时会终止应用程序。与assert
不同,它不受NDEBUG
控制,因此无论编译模式如何,检查都会执行。因此,在以下示例中,fp->Write(x)
总是会执行:
CHECK(fp->Write(x) == 4) << "Write failed!";
有多个辅助宏用于比较相等性和不等性——CHECK_EQ
、CHECK_NE
、CHECK_LE
、CHECK_LT
、CHECK_GE
和 CHECK_GT
(等、不等、小于等、小于、大于等、大于)。它们比较两个值,并在结果不符合预期时记录一个FATAL
级别的消息,包括这两个值。这两个值必须有operator<<(std::ostream, ...)
定义。
你可以像这样向错误消息添加内容:
CHECK_NE(1, 2) << ": The world must be ending!";
我们非常注意确保每个参数恰好被评估一次,并且任何合法传递给函数参数的表达式在这里也是合法的。特别是,参数可以是临时表达式,这些表达式将在语句结束时被销毁,例如:
CHECK_EQ(string("abc")[1], 'b');
如果一个参数是指针,而另一个是NULL,编译器会报告错误。为了解决这个问题,简单地将NULL转换为所需指针的类型:
CHECK_EQ(some_ptr, static_cast<SomeType*>(NULL));
更好的做法是使用CHECK_NOTNULL
宏:
CHECK_NOTNULL(some_ptr);
some_ptr->DoSomething();
由于这个宏返回给定的指针,所以在构造函数初始化列表中非常有用。
struct S {
S(Something* ptr) : ptr_(CHECK_NOTNULL(ptr)) {}
Something* ptr_;
};
请注意,由于这个特性,你不能将这个宏作为C++流使用。请使用上面描述的CHECK_EQ
来在终止应用程序之前记录自定义消息。
如果你正在比较C字符串(char *),有一套方便的宏同时执行大小写敏感和不敏感的比较——CHECK_STREQ
、CHECK_STRNE
、CHECK_STRCASEEQ
和 CHECK_STRCASENE
。CASE
版本是不区分大小写的。你可以安全地传递NULL
指针给这些宏。它们将NULL
和任何非NULL
字符串视为不等。两个NULL
是相等的。
注意,两个参数都可以是临时字符串,这些字符串在当前“完整表达式”的末尾被销毁(例如,CHECK_STREQ(Foo().c_str(), Bar().c_str())
,其中Foo
和Bar
返回C++的std::string
)。
CHECK_DOUBLE_EQ
宏检查两个浮点值的相等性,接受一个小的错误范围。CHECK_NEAR
接受第三个浮点参数,指定可接受的错误范围。
详细日志记录vlog
当你在追踪困难的错误时,详细的日志消息非常有用。然而,你可能想要在通常的开发中忽略过于详细的日志。对于这种详细日志,glog提供了VLOG
宏,它允许你定义自己的数字日志级别。--v
命令行选项控制哪些详细消息被记录:
VLOG(1) << "当我运行程序时,如果--v=1或更高,我会打印出来";
VLOG(2) << "当我运行程序时,如果--v=2或更高,我会打印出来";
使用VLOG
,详细级别越低,消息被记录的可能性越大。例如,如果--v==1
,VLOG(1)
会记录,但VLOG(2)
不会记录。这与严重级别是相反的,其中INFO
是0,ERROR
是2。--minloglevel
为1将记录WARNING
及以上级别。尽管你可以为VLOG
宏和--v
标志指定任何整数,但它们的常见值是小的正整数。例如,如果你写VLOG(0)
,你应该指定--v=-1
或更低来使其静音。这不太有用,因为我们通常不希望默认情况下记录详细的日志。VLOG
宏始终以INFO
日志级别记录(如果它们记录的话)。
你可以在命令行上按模块控制详细日志:
--vmodule=mapreduce=2,file=1,gfs*=3 --v=0
将会:
- a. 打印来自mapreduce.{h,cc}的VLOG(2)及更低级别的消息。
- b. 打印来自file.{h,cc}的VLOG(1)及更低级别的消息。
- c. 打印以"gfs"为前缀的文件的VLOG(3)及更低级别的消息。
- d. 打印来自其他地方的VLOG(0)及更低级别的消息。
©所示的通配功能支持’*‘(匹配0个或多个字符)和’?'(匹配任何单个字符)通配符。请也查看命令行标志部分。
还有VLOG_IS_ON(n)
“详细级别”条件宏。当--v
等于或大于n
时,此宏返回true。使用方式如下:
if (VLOG_IS_ON(2)) {
// 做一些日志准备和记录
// 这些不能只用VLOG(2)完成;
}
详细级别条件宏VLOG_IF
、VLOG_EVERY_N
和VLOG_IF_EVERY_N
类似于LOG_IF
、LOG_EVERY_N
和LOF_IF_EVERY
,但接受一个数字详细级别而不是严重级别。
VLOG_IF(1, (size > 1024))
<< "当size超过1024,并且你运行程序时 --v=1 或更高,我会打印";
VLOG_EVERY_N(1, 10)
<< "每10次出现一次,并且当你运行程序时 --v=1 或更高。当前出现是 " << google::COUNTER;
VLOG_IF_EVERY_N(1, (size > 1024), 10)
<< "在size超过1024的情况下,每10次出现一次,当你运行程序时 --v=1 或更高。" << "当前出现是 " << google::COUNTER;
失败信号处理器
库提供了一个方便的信号处理器,当程序在某些信号(如SIGSEGV)上崩溃时,它会转储有用的信息。可以通过google::InstallFailureSignalHandler()
安装信号处理器。以下是信号处理器输出的示例。
*** Aborted at 1225095260 (unix time) try "date -d @1225095260" if you are using GNU date ***
*** SIGSEGV (@0x0) received by PID 17711 (TID 0x7f893090a6f0) from PID 0; stack trace: ***
PC: @ 0x412eb1 TestWaitingLogSink::send()
@ 0x7f892fb417d0 (unknown)
@ 0x412eb1 TestWaitingLogSink::send()
@ 0x7f89304f7f06 google::LogMessage::SendToLog()
@ 0x7f89304f35af google::LogMessage::Flush()
@ 0x7f89304f3739 google::LogMessage::~LogMessage()
@ 0x408cf4 TestLogSinkWaitTillSent()
@ 0x4115de main
@ 0x7f892f7ef1c4 (unknown)
@ 0x4046f9 (unknown)
默认情况下,信号处理器将失败转储写入标准错误。你可以通过InstallFailureWriter()
自定义目的地。
其他注意事项
消息的性能
glog提供的有条件日志宏(例如,CHECK
、LOG_IF
、VLOG
等)被仔细实现,当条件为假时不会执行右手边的表达式。因此,以下检查可能不会牺牲你的应用程序的性能。
CHECK(obj.ok) << obj.CreatePrettyFormattedStringButVerySlow();
用户定义的失败函数
FATAL
严重级别的消息或未满足的CHECK
条件会终止你的程序。你可以通过InstallFailureFunction
改变终止行为。
void YourFailureFunction() {
// 报告一些事情...
exit(1);
}
int main(int argc, char* argv[]) {
google::InstallFailureFunction(&YourFailureFunction);
}
默认情况下,glog尝试转储堆栈跟踪并在程序上运行时退出状态为1。只有在你运行程序的架构支持堆栈跟踪时(截至2008年9月,glog支持x86和x86_64的堆栈跟踪),才会生成堆栈跟踪。
原始日志记录(Raw Logging)
glog/raw_logging.h
头文件可以用来进行线程安全的日志记录,它不会分配任何内存或获取任何锁。因此,这个头文件中定义的宏可以被用于低级别的内存分配和同步代码中。有关详细信息,请查看 src/glog/raw_logging.h.in
。
Google风格的perror()
PLOG()
、PLOG_IF()
和 PCHECK()
的行为与它们的 LOG*
和 CHECK
对应物完全相同,不同之处在于它们会在输出行的末尾附加当前 errno
状态的描述。例如:
PCHECK(write(1, NULL, 2) >= 0) << "Write NULL failed";
这个检查失败,错误信息如下:
F0825 185142 test.cc:22] Check failed: write(1, NULL, 2) >= 0 Write NULL failed: Bad address [14]
日志到syslog
SYSLOG
、SYSLOG_IF
和 SYSLOG_EVERY_N
宏可用于将日志记录到 syslog 以及普通的日志中。请注意,将日志记录到 syslog 可能会严重影响性能,特别是如果 syslog 配置为远程日志记录的话!在您使用这些宏之前,请确保您了解输出到 syslog 的影响。通常,建议谨慎使用这些宏。
剥离日志消息
日志消息中使用的字符串可能会增加您的二进制文件大小,并带来隐私问题。因此,您可以使用 GOOGLE_STRIP_LOG 宏指示 glog 移除所有低于某个严重级别的字符串,从而避免这些问题。如果您的应用中有如下代码:
#define GOOGLE_STRIP_LOG 1 // 这必须在 #include 之前!
#include <glog/logging.h>
编译器将会移除严重级别低于指定整数值的所有日志消息。由于 VLOG
日志在 INFO
严重级别(数值为 0
),将 GOOGLE_STRIP_LOG
设置为 1 或更高将移除所有与 VLOG
相关的日志消息以及 INFO
日志语句。
Windows用户的注意事项
Google glog 定义了一个严重级别 ERROR
,这在 windows.h
中也有所定义。您可以通过在包含 glog/logging.h
之前定义 GLOG_NO_ABBREVIATED_SEVERITIES
来使 glog 不定义 INFO
、WARNING
、ERROR
和 FATAL
。即使使用这个宏,您仍然可以使用类似 iostream 的日志功能:
#define GLOG_NO_ABBREVIATED_SEVERITIES
#include <windows.h>
#include <glog/logging.h>
// ...
LOG(ERROR) << "This should work";
LOG_IF(ERROR, x > y) << "This should be also OK";
然而,您将无法再在 glog/logging.h
定义的函数中使用 INFO
、WARNING
、ERROR
和 FATAL
。
#define GLOG_NO_ABBREVIATED_SEVERITIES
#include <windows.h>
#include <glog/logging.h>
// ...
// 这不会工作。
// google::FlushLogFiles(google::ERROR);
// 使用这个代替。
google::FlushLogFiles(google::GLOG_ERROR);
如果您不需要 windows.h
定义的 ERROR
,还有一些其他的解决方法,有时这些方法可能不奏效:
- 在包含
windows.h
之前定义WIN32_LEAN_AND_MEAN
或NOGDI
。 - 在包含
windows.h
之后取消定义ERROR
。
有关更多详细信息,请参见 这个问题。