fagle

Glog使用介绍与源码分析

0
阅读(9992)

 

1、简介:

最新版本: 0.3.3  2013-1-31

下载地址:http://code.google.com/p/google-glog/downloads/list

使用方便,性能也不错。

Google glog 库实现了应用级的日志记录,提供了C++ 风格的流操作和各种助手宏。

代码示例:

#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";

}

“LOG”宏为日志输出关键字,“INFO”为严重性程度。

主要支持功能:

1) 参数设置,以命令行启动参数的方式设置标志参数来控制日志记录行为;

2) 严重性分级,根据日志严重性分级记录日志;

3) 可有条件地记录日志信息;

4) 条件中止程序。丰富的条件判定宏,可预设程序终止条件;

5) 异常信号处理。程序异常情况,可自定义异常处理过程;

6) 支持debug功能。可只用于debug模式;

7) 自定义等级日志信息;

8) 原始日志记录。无需加锁与动态分配内存的轻量级线程安全版本;

9) 系统日志记录;

10) google perror风格日志信息;

11) 日志信息移除。

glog的使用是比较简单的, 几乎可以不用配置就直接使用了。在配置方式上, glog和一般的日志系统(如log4系列)是不太一样的, 后者一般使用配置文件, 而glog是在命令行参数中指定的。对比优缺点, 配置文件做的配置可能更加强大一些, 不过命令行配置虽然简单但是也不失灵活。

2、严重性分级

glog可通过根据指定的严重性等级,来选择性记录日志。日志信息严重性等级按由低到高排列依次为:INFO, WARNING, ERROR, 和 FATAL四级。使用者可以在命令行中设置严重性等级门限值来控制日志的输出,详细见“参数设置”部分的“minloglevel”标志值的介绍。

其中FATAL等级的日志会在记录以后终止程序运行,要谨慎使用。

3、参数设置

可通过命令行方式设置glog的标志参数,用来控制日志记录行为。

命令格式如下:

标志名1 = 标志值 标志名2=标志值 …… 标志值n=标志值        ./程序名

所有标志名需添加统一前缀—“GLOG_”,不同标志语句之间以空格相隔;

例如:

-- GLOG_vmodule=mapreduce=2,file=1,gfs*=3  -- GLOG_v=0 ./application.exe

常用标志参数类型及其作用说明:

logtostderr (bool, default=false)

值为true的时候,日志信息输出到stderr,并非文件。默认值为 false。

stderrthreshold (int, default=2, which is ERROR)

严重性级别在该门限值以上的日志信息除了写入日志文件以外,还要输出到stderr。各严重 性级别对应的数值:INFO—0,WARNING—1,ERROR—2,FATAL—3

默认值为2.

minloglevel (int, default=0, which is INFO)

严重性级别在该门限值以上的日志信息才进行记录。默认值为0.

log_dir (string, default="")

日志信息记录路径。默认为空,如果没有指定信息输出到stderr,则信息保存在"/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").

v (int, default=0)

对于使用“ VLOG(m)”(m为int型)表达式进行输出的日志信息,只在m的值小于该标志的值 的时候,才进行输出。另外, 该设置可能被 vmodule标志给覆盖.默认为0.

vmodule (string, default="")

分模块(文件)设置VLOG(m)日志信息的输出。基本命令格式为以逗号分开的“<module name>=<log level>”表达式组成。其中<module name> 是“glob pattern”,支持通配符,<module name>不包括文件的扩展 名(.h,.cc等)。

还有其他的标志参数定义在logging.cc,可在文件中搜索“DEFINE_”来进行查看。

也可以通过在程序中修改FLAGS_*全局变量的值来修改这些标志。当修改了FLAGS_* 以后,大部分设置立即生效,除了那些与修改目标文件相关的标志。比如:

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";

3、有条件地记录日志信息

glog可以控制日志信息在指定条件下进行记录。具体使用如下:

1) LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";

上面的语句表示,只有当num_cookies > 10条件成立时,“Got lots of cookies”日志信息才被记录。

2) LOG_EVERY_N(INFO, 10) << "Got the " << COUNTER << "th cookie";

上面的语句表示,在程序中周期性的记录日志信息,在该语句第1、11、21……次被执行的时候,记录日志 信息。COUNTER变量表示该语句被执行的次数。

3) LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Got the " << COUNTER<< "th big cookie";

上面的语句为1,2项功能的合并,size>1024的条件连续成立10次的时候记录日志信息。COUNTER变量表示该条件成立的次数。

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

上面的语句表示,当该语句只在首次执行了20次以后记录日志信息, COUNTER变量表示该语句被执行的次数。

4、有条件地中止程序

glog提供了CHECK宏,用于在调试地时候中止程序,及早发现程序错误。当通过该宏指定的条件不成立的时候,程序会中止,并且记录对应的日志信息。功能类似于ASSERT,区别是CHECK宏不受NDEBUG约束,在release版中同样有效。具体使用如下:

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

当fp->Write(x) == 4成立时,记录“Write failed!”日志信息,并且中止程序,其中fp->Write(x) == 4为判定条件,日志信息以c++的stream操作形式生成。

glog提供了多个便利的宏来处理特定关系的判定。具体有:

1) 判定大小关系

CHECK_EQ, CHECK_NE, CHECK_LE, CHECK_LT, CHECK_GE, CHECK_GT,使用这些宏需要注意类型一致,如果出现类型不一致的,可使用static_cast转换。

2) 判定指针是否为空

CHECK_NOTNULL(some_ptr),可用于对象初始化的时候。

3) 判定字符串是否相等

CHECK_STREQ, CHECK_STRNE, CHECK_STRCASEEQ, CHECK_STRCASENE。可进行大小写敏感或不敏感字符串来分别判定

4) 判定浮点是否相等或相近

CHECK_DOUBLE_EQ,CHECK_NEAR。这两个宏都需要指定一个可容忍的偏差上限。

当这些宏判定条件不成立时,glog会生成一个FATAL级别的日志信息,该信息包含比较的两个值和stream方式传入的字符串,然后中止程序。

5、异常信号处理

glog提供了比较方便的程序异常处理机制。例如,当程序出现SIGSEGV异常信号时,glog的默认异常处理过程会导出非常有用的异常信息。异常处理 过程可以通过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)

默认情况下,异常信息是输出到stderr,通过InstallFailureWriter()可以改变输出目标。

6Debug日志

glog提供特定的宏只在debug模式下生效。以下分别对应LOG、LOG_IF、DLOG_EVERY_N操作的专用宏。

DLOG(INFO) << "Found cookies";

DLOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";

DLOG_EVERY_N(INFO, 10) << "Got the " << COUNTER << "th cookie";

7、自定义等级日志

glog提供VLOG宏,让用户自定义分级信息,该分级与LOG宏对应的严重性分级是独立管理,在命令行参数设置中独立设置“v”或 “vmodule”参数来控制,具体见“参数设置”部分标志说明。VLOG宏便于用户调试、查找完问题以后,屏蔽日志信息,减轻负担。具体使用如下:

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";

上面的语句,只有在size>1024成立时且命令行参数v的值不小于1,才记录日志信息。

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 " << COUNTER;

上面的语句,只有在命令行参数v的值不小于1时,才会每执行10次记录一次日志信息。

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 " << COUNTER;

上面的语句,只有在命令行参数v的值不小于1时,若size>1024条件连续成立10次,记录一次日志信息。

8、原始日志记录

glog提供了线程安全的原始日志记录方式。在<glog/raw_logging.h>文件中提供了相关的宏, 如,RAW_CHECK,RAW_LOG等。这些宏的功能与CHECK,LOG等一致,不需要为其动态分配内存和提供额外的锁 (lock)机制。因此,这些宏可以用在较为底层的allocation(内存分配)和synchronization(同步)等功能的代码中。

9、系统日志记录

glog除了提供了普通的日志记录宏,还提供SYSLOG, SYSLOG_IF,和 SYSLOG_EVERY_N宏,这些宏将日志信息通过syslog()函数记录到系统日志。

10google perror风格日志信息

glog提供了与LOG*和CHECK宏作用等价的PLOG()、PLOG_IF() 和PCHECK()宏,不同的是,后者在记录日志信息的时候,会将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]

11、日志信息移除

日志信息的字符串会占用比较大的内存空间,另外还带来隐私泄露的问题。glog提供了GOOGLE_STRIP_LOG宏在编译时候去除日志的字符串信息。

12、一般的日志输出流程

glog一般使用VLOG(num)或者LOG(severity)两种形式的宏进行输出.就以LOG(severity)为例进行说明, VLOG(num)系列应该类似了.

比如要输出一条日志信息, 如
LOG(ERROR) << "hello world"

LOG宏的定义是:

#define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()

可以看到它将根据具体的名字展开为另一个宏,因为上面选择的输出级别是ERROR, 所以展开的名称为COMPACT_GOOGLE_LOG_ERROR。

继续跟进这个宏的定义:

#define COMPACT_GOOGLE_LOG_ERROR google::LogMessage( \

__FILE__, __LINE__, google::GLOG_ERROR)

可以看到这个宏的作用其实就是创建了一个名为LogMessage的类, 构造函数的输入参数包括了:文件名, 行号, 日志级别

继续跟进LogMessage的构造函数:

LogMessage::LogMessage(const char* file, int line, LogSeverity severity)

: allocated_(NULL) {

Init(file, line, severity, &LogMessage::SendToLog);

}

这一次, 多了一个参数, 名为SendToLog的类成员函数指针。这里需要说明的是, 其实这个函数指针参数的目的是进行日志输出的操作,但是是可以配置的,因为LogMessage还有另外重载的构造函数其中有这个参数的,只是这里说明的是最简单的情况,所以就考虑了这个函数指针默认为SendToLog的情况,从名字可以猜测到,该函数的作用是向磁盘进行日志输出操作,另外glog中还可以向标准输出进行输出,如果有必要,应该还可以通过网络对某个地址端口进行输出–这些情况我都没有进一步跟进了,只想说明该函数指针的作用是向不同的介质输出日志,而使用函数指针作为参数就是为了让这个行为可以配置。

继续往下走,看看LogMessage::Init函数做的事情.这里不贴代码了,有兴趣的可以自己跟进看看。简单来说做的事情是:初始化日志输入的流缓冲区,初始化该日志的时间,格式,找到日志打印的文件名等。

好了,至此,一个完整的LogMessage就创建完毕。可以看到,在glog中,任何的一条日志信息,最终都会对应到一个新创建的LogMessage对象。

上面的日志输出流程,其实还没有完,因为还要进行流输入操作呢,就是输入”hello world”字符串。没错,退回头看看LOG宏的定义

#define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()

 

如果说,前面的COMPACT_GOOGLE_LOG_ ## severity创建了一个LogMessage类对象,那么其实这个宏最终的结果是返回这个LogMessage类对象的成员stream(), 在提到LogMessage::Init函数的时候就提到过,该函数初始化了流输入缓冲区。

来看LogMessage类中该流输入类的定义:

#ifdef _MSC_VER

# pragma warning(disable: 4275)

#endif

class GOOGLE_GLOG_DLL_DECL LogStream : public std::ostream {

#ifdef _MSC_VER

# pragma warning(default: 4275)

#endif

public:

LogStream(char *buf, int len, int ctr)

: std::ostream(NULL),

streambuf_(buf, len),

ctr_(ctr),

self_(this) {

rdbuf(&streambuf_);

}

int ctr() const { return ctr_; }

void set_ctr(int ctr) { ctr_ = ctr; }

LogStream* self() const { return self_; }

// Legacy std::streambuf methods.

size_t pcount() const { return streambuf_.pcount(); }

char* pbase() const { return streambuf_.pbase(); }

char* str() const { return pbase(); }

private:

base_logging::LogStreamBuf streambuf_;

int ctr_; // Counter hack (for the LOG_EVERY_X() macro)

LogStream *self_; // Consistency check hack

};

其实很简单, 从std::ostrstream中继承过来,构造函数中有一个缓冲区就好了.在LogMessage::Init类中,定义该缓冲区的大小为:

data_->buf_ = new char[kMaxLogMessageLen+1];

其中

const size_t LogMessage::kMaxLogMessageLen = 30000;

这里可以看到glog限制的一条日志长度大小为30000 byte。

好了,到此为止,流输入也有了,尽管往里面写数据就好。那么什么时候进行输出呢?glog采用的是在这个创建的临时LogMessage对象(说它是”临时对象”是因为它是匿名的,因此不会保存,创建了就会被释放)被释放的时候,在析构函数中做输出操作:

LogMessage::~LogMessage() {

Flush();

delete allocated_;

}

Flush函数不详细分析了,主要做的事情就是将日志和之前得到的日期等进行格式化,最后调用注册的输出日志用的函数指针进行输出操作。

到此为止,一条glog日志就输出完成了。

再回头看看,实际上这里应该还有一些东西是需要全局使用的,比如有多条日志同时向一个文件进行输出的时候,需要对文件进行加锁,还比如要更新一些统计的数据,如每个级别的日志都有多少条了。这些在glog中都是全局变量:

static Mutex log_mutex;

// Number of messages sent at each severity. Under log_mutex.

int64 LogMessage::num_messages_[NUM_SEVERITIES] = {0, 0, 0, 0};

当然,这里的num_messages_数组准确的说是LogMessage的静态成员变量,但是大体理解为全局变量也不为错,因为这个数据全局都只有一份了。

 

image