Google Logging (glog) 是一个 C++98 库,用于实现应用程序级日志记录。该库提供了基于 C++ 风格流和各种帮助宏的日志 API。
您可以通过简单地将内容流式传输到LOG(严重性级别
)来记录消息,例如,
#include <glog/logging.h>
int main(int argc, char* argv[]) {
// Initialize Google’s logging library.
google::InitGoogleLogging(argv[0]);
// ...
LOG(INFO) << "Found " << num_cookies << " cookies";
}
git clone https://github.com/google/glog.git
cd glog
cmake -S . -B build -G "Unix Makefiles"
cmake --build build
cmake --build build --target test
cmake --build build --target install
如果您的系统中安装了 glog,您可以使用 CMake 命令 find_package
在您的 CMake 项目中针对 glog 进行构建,如下所示:
cmake_minimum_required (VERSION 3.0.2)
project (myproj VERSION 1.0)
find_package (glog 0.5.0 REQUIRED)
add_executable (myapp main.cpp)
target_link_libraries (myapp glog::glog)
编译定义和选项将根据需要自动添加到您的目标程序中。
glog 定义了一系列宏来简化许多常见的日志任务。您可以按严重性级别记录日志消息、从命令行控制日志记录行为、基于条件记录日志、在不满足预期条件时中止程序、引入您自己的详细的日志记录级别、自定义附加到日志消息的前缀等。
以下部分描述了 glog 支持的功能。
您可以指定以下的严重性级别(严重程度的增加顺序): INFO
,WARNING
,ERROR
和FATAL
。记录FATAL
消息会终止程序(在记录消息之后)。请注意,给定严重性级别的消息不仅会记录在该严重性级别对应的日志文件中,还会记录在所有较低严重性级别的日志文件中。例如,严重性级别为的FATAL
的消息将被记录到严重性级别为FATAL
,ERROR
,WARNING
和INFO
的日志文件中。
DFATAL
严重性级别记录了一个FATAL
错误在调试模式下,但通过自动降低其严重性级别为ERROR在生产环境中暂停程序。
除非另有说明,glog 会写入文件/tmp/\<program name\>.\<hostname\>.\<user name\>.log.\<severity level\>.\<date\>.\<time\>.\<pid\>
(例如, /tmp/hello_world.example.com.hamaji.log.INFO.20080709-222411.10474
)。默认情况下,除了日志文件之外,glog 还会复制严重级别为ERROR
或 FATAL
的日志消息到标准错误 (stderr
)。
几个标志会影响 glog 的输出行为。如果您的机器上安装了Google gflags
库,构建系统将自动检测并使用它,允许您在命令行上传递标志。例如,如果您想打开标志--logtostderr
,您可以使用以下命令行启动您的应用程序:
./your_application --logtostderr=1
如果未安装 Google gflags
库,您可以通过环境变量设置标志,在标志名称前加上前缀GLOG_
,例如,
GLOG_logtostderr=1 ./your_application
以下标志是最常用的:
logtostderr
( bool
, 默认= false
)stderr
而不是日志文件。注意:您可以通过指定1
、true
、 或yes
(不区分大小写)来设置二进制标志为true
。此外,您可以通过指定0
, false
, 或no
(同样不区分大小写)来设置二进制标志为false
。stderrthreshold
( int
, 默认=2
, 即ERROR
)stderr
。严重性等级INFO
、WARNING
、 ERROR
和FATAL
对应的数字分别为 0
、 1
、 2
和 3
。minloglevel
( int
, 默认=0
, 即INFO
)记录处于或高于此级别的消息
。同样,严重性等级INFO
、WARNING
、 ERROR
和FATAL
对应的数字分别为 0
、 1
、 2
和 3
。log_dir
(string
,默认=“”)v
( int
, 默认=0
)VLOG(m)
消息m
。由--vmodule
重载。vmodule
(string
,默认=“”)<module name>=<log level>
列表。<module name>
是一个glob
模式(例如,gfs*
对于名称以“gfs”开头的所有模块),与文件名基础匹配(即忽略 .cc/.h./-inl.h
的名称)。<log level>
覆盖由--v
给出的任何值。您还可以通过修改全局变量来修改程序中的标志值FLAGS_*
。大多数设置在您更新FLAGS_*
后立即开始工作。与目标文件相关的标志是例外。例如,您可能需要在调用google::InitGoogleLogging
之前设置FLAGS_log_dir
。下面是一个例子:
LOG(INFO) << "file";
// Most flags work immediately after updating values.
FLAGS_logtostderr = 1;
LOG(INFO) << "stderr";
FLAGS_logtostderr = 0;
// This won’t change the log destination. If you want to set this
// value, you should do this before google::InitGoogleLogging .
FLAGS_log_dir = "/some/log/directory";
LOG(INFO) << "the same file";
有时,您可能只想在特定条件下记录消息。您可以使用以下宏来执行条件日志记录:
LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
仅当变量num_cookies
超过10时才会记录“Got lot of cookies”消息 。如果一行代码被多次执行,则在特定时间间隔内记录一条消息可能会很有用。这种日志记录对于信息性消息最有用。
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 次输出一条消息,您还可以将输出限制只在前 n 次出现时输出:
LOG_FIRST_N(INFO, 20) << "Got the " << google::COUNTER << "th cookie";
输出执行前20次的日志消息。同样, google::COUNTER
的值用于标识正在发生的重复。
其他时候,只希望根据时间定期记录消息。例如,每 10 毫秒记录一条消息:
LOG_EVERY_T(INFO, 0.01) << "Got a cookie";
或者每 2.35 秒记录一条消息:
LOG_EVERY_T(INFO, 2.35) << "Got a cookie";
特殊的“调试模式”日志宏仅在调试模式下有效,并且在非调试模式编译时被编译为空。使用这些宏可以避免由于过多的日志记录而减慢应用程序的速度。
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<<(ostream, ...)
定义。
您可以像这样附加到错误消息:
CHECK_NE(1, 2) << ": The world must be ending!";
我们非常小心地确保每个参数只计算一次,并且任何合法的可以作为函数参数传递的东西在这里都是合法的。特别是,参数可能是临时表达式,最终会在语句的末尾被销毁,例如:
CHECK_EQ(string("abc")[1], ’b’);
如果参数之一是指针而另一个是NULL
,则编译器会报告错误。要解决此问题,只需static_cast
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
接受第三个浮点参数,它指定可接受的误差范围。
当您在寻找困难的错误时,完整的日志消息非常有用。但是,您可能希望在通常的开发中忽略过于冗长的消息。对于这种详细的日志记录,glog 提供了VLOG
宏,它允许您定义自己的日志记录级别。在--v
命令行选项控制哪些等级的日志被记录:
VLOG(1) << "I’m printed when you run the program with --v=1 or higher";
VLOG(2) << "I’m printed when you run the program with --v=2 or higher";
使用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
这将会:
所示的通配符功能支持 ‘*’(匹配 0 个或多个字符)和 ‘?’ (匹配任何单个字符)。
还有VLOG_IS_ON(n)
“详细级别”条件宏。当--v
等于或大于n
时,此宏返回真。用作
if (VLOG_IS_ON(2)) {
// do some logging preparation and logging
// that can’t be accomplished with just VLOG(2) << ...;
}
详细级别条件宏VLOG_IF
,VLOG_EVERY_N
和VLOG_IF_EVERY_N
行为类似于LOG_IF
,LOG_EVERY_N
,LOF_IF_EVERY
,但接受代表详细级别的数字而不是严重性级别。
VLOG_IF(1, (size > 1024))
<< "I’m printed when size is more than 1024 and when you run the "
"program with --v=1 or more";
VLOG_EVERY_N(1, 10)
<< "I’m printed every 10th occurrence, and when you run the program "
"with --v=1 or more. Present occurence is " << google::COUNTER;
VLOG_IF_EVERY_N(1, (size > 1024), 10)
<< "I’m printed on every 10th occurence of case when size is more "
" than 1024, when you run the program with --v=1 or more. ";
"Present occurence is " << google::COUNTER;
glog 支持通过接收用户提供的用于生成此类字符串的回调来更改附加到日志消息的前缀的格式。该功能必须在编译时由WITH_CUSTOM_PREFIX
标志启用。
对于每个日志条目,将使用包含严重性、文件名、行号、线程 ID 和事件时间的LogMessageInfo
结构调用回调。它还将获得对输出流的引用,其内容将被添加到最终日志行中的实际消息之前。
例如:
/* This function writes a prefix that matches glog's default format.
* (The third parameter can be used to receive user-supplied data, and is
* NULL by default.)
*/
void CustomPrefix(std::ostream &s, const LogMessageInfo &l, void*) {
s << l.severity[0]
<< setw(4) << 1900 + l.time.year()
<< setw(2) << 1 + l.time.month()
<< setw(2) << l.time.day()
<< ' '
<< setw(2) << l.time.hour() << ':'
<< setw(2) << l.time.min() << ':'
<< setw(2) << l.time.sec() << "."
<< setw(6) << l.time.usec()
<< ' '
<< setfill(' ') << setw(5)
<< l.thread_id << setfill('0')
<< ' '
<< l.filename << ':' << l.line_number << "]";
}
为了能够使用CustomPrefix()
,在记录初始化过程中传入它的指针:InitGoogleLogging(argv[0], &CustomPrefix);
。
可选地,InitGoogleLogging()
将void *
类型的第三个参数传递给回调函数。
该库提供了一个方便的信号处理程序,当程序在某些信号上崩溃时,它会转储有用的信息,例如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() {
// Reports something...
exit(1);
}
int main(int argc, char* argv[]) {
google::InstallFailureFunction(&YourFailureFunction);
}
默认情况下,glog 会尝试转储堆栈跟踪并使程序以状态 1 退出。仅当您在 glog 支持堆栈跟踪的架构上运行程序时才会生成堆栈跟踪。
头文件<glog/raw_logging.h>
可用于线程安全日志记录,它不分配任何内存或获取任何锁。因此,这个头文件中定义的宏可以被低级内存分配和同步代码使用。
PLOG()
,PLOG_IF()
和PCHECK()
的行为与LOG*
和CHECK
完全一样 ,除了它们将error
的当前状态的描述附加到它们的输出行之外。例如
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_IF
和SYSLOG_EVERY_N
宏可用。除了正常日志之外,这些日志还记录到 syslo
g。请注意,将日志记录到 syslog
会极大地影响性能,尤其是在将syslog
配置为远程日志记录的情况下!在使用这些宏之前,请确保您了解输出到syslog
的含义。一般来说,谨慎使用这些宏是明智的。
日志消息中使用的字符串会增加二进制文件的大小并带来隐私问题。因此,您可以使用GOOGLE_STRIP_LOG
宏命令 glog 删除所有低于特定严重性级别的字符串 :
如果您的应用程序有这样的代码:
#define GOOGLE_STRIP_LOG 1 // this must go before the #include!
#include <glog/logging.h>
编译器将删除严重性小于指定整数值的日志消息。由于VLOG
日志处于严重级别 INFO
(数值0
),因此设置GOOGLE_STRIP_LOG
为1
或更高会删除与VLOG
相关联的所有日志消息以及INFO
日志语句。
要启用日志清理器:
google::EnableLogCleaner(3); // keep your logs for 3 days
然后 glog 会在执行刷新时检查是否有过期日志。在此示例中,您项目中最后修改时间超过 3 天的任何日志文件都将被unlink()
。
可以随时禁用此功能(如果已启用)
google::DisableLogCleaner();