Glog 简介

Google 日志库(glog)简介

简介

Google glog 是一个实现应用级别日志的库。该库提供基于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";
}

Google glog 定义了一系列宏用于简化很多常见的日志任务。你可以按照严重级别记录日志、使用命令行控制日志行为、基于条件记录日志、预期情形没有出现时退出程序、引入你自己的日志等级,等等。该文档介绍了glog支持的一些功能。请注意,该文档只介绍了该库最有用的一些功能,并非全部。如果你想了解一些比较不常使用的功能,你可以查看 src/glog 目录下面的头文件。

严重级别

你可以使用以下严重级别(按照严重性递增):INFOWARNINGERRORFATAL。记录 FATAL 日志会(在信息记录完成后)终止程序。注意,特定级别的信息并非只保存到对应的日志文件,还会保存到低等级的日志文件。例如FATAL的日志会保存到 FATALERRORWARNINGINFO对应的文件。

DFATAL 等级在调试模式下记录 FATAL 信息(注意,没有定义宏 NDEBUG),但不会像生产环境中那样终止程序执行,而是自动降低到 ERROR 级别。

除非特别指定,glog日志文件名格式为 “/tmp/<program name>.<hostname>.<username>.log.<severity level>.<date>.<time>.<pid>”。(例如"/tmp/hello_world.example.com.hamaji.log.INFO.20080709-222411.10474")。默认情况下,glog还会把ERRORFATAL级别的信息打印到标准输出。

设置标记

很多标记会影响 glog 的输出行为。如果你安装了Google gflags libraryconfigure 脚本(查看软件包中的INSTALL脚本了解该脚本的详细信息)会自动检测并使用该库,从而允许你通过命令行传递标记。例如,如果你想启用 –logtostderr 标记,你可以用下面的命令行启动程序:

./your_application --logtostderr=1

如果你没有安装 Google gflags 库,你可以通过在标记名称前面使用 “GLOG_”前缀的环境变量设置标记,例如:

GLOG_logtostderr=1 ./your_application

下面是一些通常会用到的标记:

logtostderr (bool, default=false)
    将日志输出到 stderr 而不是日志文件。
    注意:你可以使用1、true或者yes(大小写敏感)设置二值标记为真。同样,你也可以使用 0、false或者no(大小写敏感)设置二值标记为假。
stderrthreshold (int, default=2, which is ERROR)
    除了将信息输出到日志文件外,还将等于、高于该等级的信息输出到stderr。严重级别 INFO、WARNING、ERROR 和 FATAL 分别对应数字0、1、2和3.
minloglevel (int, default=0, which is INFO)
    只记录不小于该等级的日志信息。同样,严重级别 INFO、WARNING、ERROR 和 FATAL 分别对应数字0、1、2和3.
log_dir (string, default="")
    如果指定了该标记,日志文件会被写到该目录而不是默认日志目录。
v (int, default=0)
    显示所有不大于该等级的日志信息。可以被 --vmodule 覆盖。可以阅读详细日志章节进一步了解该标记。
vmodule (string, default="")
    每个模块的详细日志级别。该参数由逗号分隔的 <module name>=<log level> 对组成。<module name> 是一个全局模式(例如 gfs* 代表所有以 “gfs”开头的模块),和文件名匹配(即忽略.cc/.h/-inl.h 后缀)。<log level> 会覆盖 --v 指定的值。阅读详细日志章节了解该标记。

logging.cc 中还定义了一些其它的标记。你可以通过用 grep 在该文件中搜索关键字 "DEFINE_" 获取标记的完整列表。

你也可以通过在你的程序中更改全局变量 FLAGS* 改变标记的值。大部分设定在你更新 FLAGS* 后就会立即生效。和目标文件相关的标记例外。例如,你需要在调用 google::InitGoogleLogging 之前设置标记 FLAGSlogdir,下面是个具体例子:


   LOG(INFO) << "file";

   // 大部分标记在更新值后立即生效

   FLAGSlogtostderr = 1;

   LOG(INFO) << "stderr";

   FLAGSlogtostderr = 0;

   // 这不会改变日志目录。如果你想要设置这个值,你要在调用

   // google::InitGoogleLogging 之前设置。

   FLAGSlogdir = "/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次记录一条日志不同,你也可以只记录前面 n 次:

LOG_FIRST_N(INFO, 20) << "Got the " << google::COUNTER << "th cookie";

上面的语句只会记录前面 20 次执行。

调试模式支持

特殊的 “调试模式” 日志宏只在调试模式下有效、而在非调试模式编译时会被编译去除。使用下面的宏避免在生产环境中由于庞大的日志拖慢你的程序。

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 控制,因此 check 的执行和编译模式无关。因此,下面例子中的 fp->Write(x) 总会执行。

CHECK(fp->Write(x) == 4) << "Write failed!";

这里有很多好用的等值/不等值宏 – CHECKEQ, CHECKNE, CHECKLE, CHECKLT, CHECKGE, 以及 CHECKGT。它们会比较这两个值,如果比较结果和预期不同,就会以 FATAL日志形式记录这两个值。值必须定义了 <<(ostream,…) 操作符。

你可以像下面这样追加错误信息:

CHECK_NE(1, 2) << ": The world must be ending!";

我们尽可能保证每个参数都只会计算一次值,任何可以作为函数参数传递的东西在这里都合法。尤其是、参数可能是该语句执行后就会被删除的临时表达式,例如:

CHECK_EQ(string("abc")[1], 'b');

如果上面的一个参数一个是指针而另一个为空,编译器就会报错。为了解决这个问题,你可以使用 static_cast 将 NULL 转换为需要的指针类型。

CHECK_EQ(some_ptr, static_cast<SomeType*>(NULL));

更好的办法是使用 CHECKNONULL 宏:

Better yet, use the CHECKNOTNULL macro:

CHECK_NOTNULL(some_ptr);
some_ptr->DoSomething();

由于该宏返回指定的指针,因此在构造函数初始化列表中非常有用。

struct S {
    S(Something* ptr) : ptr_(CHECK_NOTNULL(ptr)) {}
    Something* ptr_;
};

注意由于它的特性你不能像 C++ 流那样使用这个宏。请使用上面介绍的 CHECK_EQ 在退出程序之前记录自定义的信息。

如果你想和 C 字符串(char*)比较,一些好用的宏可以进行大小写敏感(不敏感)比较 – CHECKSTREQ, CHECKSTRNE, CHECKSTRCASEEQ, 以及 CHECKSTRCASENE。 带有 CASE 的是大小写敏感的。你可以放心地传递空指针给这些宏。它们认为空指针和任何非空字符串是不相等的,两个空指针之间是相等的。

注意,两个参数都可以是在当前表达式之后会被析构的临时字符串(例如 CHECKSTREQ(Foo().cstr(), Bar().c_str()),其中 Foo 和 Bar 函数都返回 C++ 的 std::string)。

CHECKDOUBLEEQ 用于比较两个浮点类型是否相等,允许一些小的误差。 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==1VLOG(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} 中 VLG(1) 及以下的信息。

c. 打印用 “gfs” 作为前缀的文件中 VLOG(3) 及以下的信息。

d. 打印所有文件中 VLOG(0) 以及以下的信息。

c 中使用的通配符功能支持 ‘*’(匹配一个或多个字符)以及‘?’(匹配一个字符)。

还有VLOGISON(n) 条件宏。当 –v 等于大于n的时候这个宏会返回真,例如:

if (VLOG_IS_ON(2)) {
    // do some logging preparation and logging
    // that can't be accomplished with just VLOG(2) << ...;
}

详细等级条件宏 VLOGIF, VLOGEVERYN 以及 VLOGIFEVERYNLOG_IF, LOGEVERYN, LOFIFEVERY 类似,但接受一个数值详细等级,而不是严重级别。

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;

错误信号处理

该库还提供了一个非常方便的信号处理器,当程序由于类似 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 支持调用栈的体系结构上运行你的程序时才会导出调用找。(截至2008年9月, glog支持 x86 和 x86_64体系结构上的堆栈跟踪

原生日志

头文件 <glog/raw_logging.h> 能用于线程安全日志记录,它不需要分配任何内存或者获取任何锁。因此,该头文件里定义的宏能用于低层次的内存分配和同步代码。查看 src/glog/raw_logging.h 了解详细信息。

Google 样式的 perror()

PLOGPLOG_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]

事例程序:


Reference: glog document

Tagged on:

发表评论

电子邮件地址不会被公开。