glog是google的的一个C++轻量日志库,支持常用的日志功能:例如日志级别,自定义级别,条件输出,DEBUG日志等,同时支持gflag的参数配置方式。
本文主要介绍glog的一些基本用法。
1. 日志输出目录
日志默认打印到屏幕,同时有一句提示
“WARNING: Logging before InitGoogleLogging() is written to STDERR”
加了这一句后google::InitGoogleLogging(argv[0])
,日志输出到文件。
默认的输出位置在tmp下,文件名格式为:
/tmp/<program name>.<hostname>.<username>.log.<severity level>.<data>.<time>.<pid>
比如:introduction.ymac.yingshin.log.ERROR.20160102-231949.11182
2. 日志级别
日志级别有4种,INFO WARNING ERROR FATAL,在glog/log_severity.h
中定义,数值越大级别越高(severity)。
const int GLOG_INFO = 0, GLOG_WARNING = 1, GLOG_ERROR = 2, GLOG_FATAL = 3, NUM_SEVERITIES = 4;
const int INFO = GLOG_INFO, WARNING = GLOG_WARNING, ERROR = GLOG_ERROR, FATAL = GLOG_FATA;
FATAL日志会在打印日志后退出,并产生一个core文件,例如:
F0108 19:13:50.348691 16848 demo.cpp:10] Hello, glog fatal
*** Check failure stack trace: ***
@ 0x408a2c google::LogMessage::Fail()
@ 0x408984 google::LogMessage::SendToLog()
@ 0x408419 google::LogMessage::Flush()
@ 0x40b1da google::LogMessageFatal::~LogMessageFatal()
@ 0x405241 main
@ 0x7f4e535ecbd5 __libc_start_main
@ 0x405049 (unknown)
Aborted (core dumped)
不同的日志级别会输出到不同的文件,与日志级别一样,文件名后缀共有4个,分别是.INFO .WARNING .ERROR .FATAL。
X级别的日志会输出到所有<=X的日志文件里,例如error日志在.INFO .WARNING .ERROR日志里都会输出。
注意还有一种日志级别是DFATAL,当定义NDEBUG宏时是ERROR,否则是FATAL。主要通过在NDEBUG下定义为ERROR,否则为FATAL来实现。
// glog/log_severity.h
// DFATAL is FATAL in debug mode, ERROR in normal mode
#ifdef NDEBUG
#define DFATAL_LEVEL ERROR
#else
#define DFATAL_LEVEL FATAL
#endif
例如:
LOG(DFATAL) << "Hello, glog fatal.";
两种情况下的输出分别为:
//MACRO NDEBUG is defined.
E0113 20:32:17.928611 23650 demo.cpp:27] Hello, glog fatal.
//MACRO NDEBUG is not defined.
F0113 20:36:49.486486 32554 demo.cpp:27] Hello, glog fatal.
*** Check failure stack trace: ***
@ 0x408b90 google::LogMessage::Fail()
@ 0x408ae8 google::LogMessage::SendToLog()
@ 0x40857d google::LogMessage::Flush()
@ 0x4083b1 google::LogMessage::~LogMessage()
@ 0x4053c0 main
@ 0x7fbdd9722bd5 __libc_start_main
@ 0x4050f9 (unknown)
Aborted (core dumped)
3. 配置
glog有很多配置项,这些配置项都是通过gflags设置的,关于gflags的使用可以参考这篇文章。
例如对于logtostderr这个配置,有3种方法可以设置:
- 设置GLOG_前缀名的系统环境变量:
export GLOG_logtostderr=1;./your_application
- 手动在程序里修改gflags配置变量:
FLAGS_logtostderr = 1;LOG(INFO) << ...
。
注意FLAGS_log_dir
比较特殊,如果想要生效,需要再调用google::InitGoogleLogging
之前。 - 程序启动时在命令行配置gflags:
./your_application --logtostderr=1
注意第三种方法需要安装了gflags库,我在编译安装glog时手动指定了gflags的位置:--with-gflags=gflags安装路径
。编译的glog库是否支持gflags可以参考下logging.h这一行
//1表示支持,0表示不支持
#if 1
#include <gflags/gflags.h>
#endif
推荐使用第三种方法。
介绍下最常用的几个flag:
- logtostderr: bool型,设置为true后日志不输出到文件,在屏幕打印,默认值为false。
- stderrthreshold: int型,>=该值的日志级别除了输出到文件,同时在屏幕打印,默认值为2,即屏幕和日志同时输出ERROR FATAL错误。
- minloglevel: int型,>=该值的日志级别才会输出到文件,默认值为0,即大于等于INFO的日志会输出的文件,也就是全部输出。
- log_dir: string型,日志输出位置,默认为”“。
- v:自定义日志级别
- vmodele:自定义文件日志级别
4. 条件输出
条件输出是指,当某些变量满足某些条件时才输出日志。我在实际使用时也经常有这种需求,适用于全部输出磁盘IO可能有问题的情况。使用条件输出可以起到一个抽样的作用。
- LOG_IF(INFO, i > 5),当条件(i > 5)满足时输出日志。
- LOG_EVERY_N(INFO, 3),当运行该语句时,第1,3+1,6+1,…时输出日志,google::COUNTER记录语句的执行次数。
- LOG_IF_EVERY_N(INFO, (i > 5), 2),1 2的结合,当满足某些条件时,每N次输出日志。
- LOG_FIRST_N(INFO, 4), 此语句的前N次输出到日志,google::COUNTER记录语句的执行次数。
一个测试demo如下:
#include "glog/logging.h"
int main(int argc, char* argv[]) {
google::InitGoogleLogging(argv[0]);
for (int i = 0; i < 10; ++i) {
LOG_IF(INFO, i > 5) << "LOG_IF i > 5 i:" << i;
LOG_EVERY_N(INFO, 3) << "LOG_EVERY_N 3 i:" << i << " google::COUNTER:" << google::COUNTER;
LOG_IF_EVERY_N(INFO, (i > 5), 2) << "LOG_IF_EVERY_N i > 5 2 i:" << i;
LOG_FIRST_N(INFO, 4) << "LOG_FIRST_N 4 i:" << i << " google::COUNTER:" << google::COUNTER;
}
return 0;
}
程序输出为:
I0110 23:19:16.891237 17039 demo1.cpp:25] LOG_EVERY_N 3 i:0 google::COUNTER:1
I0110 23:19:16.891355 17039 demo1.cpp:27] LOG_FIRST_N 4 i:0 google::COUNTER:1
I0110 23:19:16.891362 17039 demo1.cpp:27] LOG_FIRST_N 4 i:1 google::COUNTER:2
I0110 23:19:16.891367 17039 demo1.cpp:27] LOG_FIRST_N 4 i:2 google::COUNTER:3
I0110 23:19:16.891371 17039 demo1.cpp:25] LOG_EVERY_N 3 i:3 google::COUNTER:4
I0110 23:19:16.891376 17039 demo1.cpp:27] LOG_FIRST_N 4 i:3 google::COUNTER:4
I0110 23:19:16.891381 17039 demo1.cpp:24] LOG_IF i > 5 i:6
I0110 23:19:16.891386 17039 demo1.cpp:25] LOG_EVERY_N 3 i:6 google::COUNTER:7
I0110 23:19:16.891389 17039 demo1.cpp:26] LOG_IF_EVERY_N i > 5 2 i:6
I0110 23:19:16.891393 17039 demo1.cpp:24] LOG_IF i > 5 i:7
I0110 23:19:16.891397 17039 demo1.cpp:24] LOG_IF i > 5 i:8
I0110 23:19:16.891402 17039 demo1.cpp:26] LOG_IF_EVERY_N i > 5 2 i:8
I0110 23:19:16.891405 17039 demo1.cpp:24] LOG_IF i > 5 i:9
I0110 23:19:16.891409 17039 demo1.cpp:25] LOG_EVERY_N 3 i:9 google::COUNTER:10
注意google::COUNTER
只有条件输出时才生效的,实际使用时见过这样的例子,没有意义,数据全为0:
if (tera::ErrorCode::kOK != code) {
if (tera::ErrorCode::kNotFound != code) {
tera_failed = true;
time_t t = time(NULL);
LOG(WARNING) <<
"printall read failed but ret code is not kNotFound count:" << google::COUNTER;
}
}
5. DEBUG日志
在debug模式下可用的日志输出的宏
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";
6. 自定义日志
glog提供了VLOG宏用于自定义日志的输出,用户可以使用–v=数字来指定自定义的日志级别。
与系统日志级别不同的是,vlog数值越低,级别越高。例如:
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 -logtostderr=1
I0119 22:53:17.962025 26849 vlog_main.cpp:23] I'm printed when you run the program with --v=1 or higher
$ ./vlog -v=2 -logtostderr=1
I0119 22:53:19.023028 26871 vlog_main.cpp:23] I'm printed when you run the program with --v=1 or higher
I0119 22:53:19.023130 26871 vlog_main.cpp:24] I'm printed when you run the program with --v=2 or higher
同时,更精彩的是自定义日志可以定义到文件级别,使用–vmodule=mapreduce=2,file=1,gfs*=3这种形式。
例如有两个文件main.c,hello.h,代码如下:
//main.cpp
#include <glog/logging.h>
#include "hello.h"
int main(int argc, char* argv[]) {
google::InitGoogleLogging(argv[0]);
google::ParseCommandLineFlags(&argc, &argv, true);
VLOG(1) << "main VLOG(1)";
VLOG(2) << "main VLOG(2)";
hello();
google::ShutDownCommandLineFlags();
return 0;
}
//hello.h
#ifndef _HELLO_H
#define _HELLO_H
#include <glog/logging.h>
void hello() {
VLOG(1) << "hello VLOG(1)";
VLOG(2) << "hello VLOG(2)";
}
#endif //_HELLO_H
编译后运行./main -v=2 --vmodule=main=2,hello=1 --logtostderr=1
输出:
I0119 23:05:52.615139 9245 main.cpp:8] main VLOG(1)
I0119 23:05:52.615236 9245 main.cpp:9] main VLOG(2)
I0119 23:05:52.615243 9245 hello.h:7] hello VLOG(1)
可以看到hello和main里使用了不同的自定义输出标准。
VLOG_IS_ON(n)用于判断级别n是否会输出到日志,即当–v指定的值>=n时返回true。
VLOG_IF, VLOG_EVERY_N,VLOG_IF_EVERY_N与LOG_XXX一致,只不过参数里的级别为自定义级别。
7. CHECK宏
CHECK系列的宏跟assert很像,都是检测某个表达式是否为真,不过不受NDEBUG影响
CHEKCK(expression) << "check failed description"
检测expression如果不为真,则打印后面的description和栈上的信息,然后退出程序,出错后的处理过程和FATAL比较像。
除了CHECK,还有 CHECK_EQ,CHECK_NE,CHECK_LE,CHECK_LT,CHECK_GE,CHECK_GT一系列宏用于判断。
空指针的判断需要转化下,例如CHECK_EQ(p, static_cast<int*>(NULL)
,直接CHECK_EQ(p, NULL)
会报错。
字符串的比较需要使用 CHECK_STREQ,CHECK_STRNE,CHECK_STRCASEEQ,CHECK_STRCASENE,注意这种情况下可以传入NULL,例如CHECK_STREQ(NULL, NULL)可以正常运行。
8. TIPS
8.1. 低级别日志的内容仍然会执行
例如:
FLAGS_minloglevel = 1;
LOG(INFO) << "info log";
LOG(INFO) << PackageToString(1);
LOG(WARNING) << "warning log";
虽然LOG(INFO)
所在的行不会打印,但是PackageToString
还是会执行,需要注意是否带来状态的变化或者性能损耗。