glog是google开源的C++日志库,提供了流式日志操作和各种方便的宏定义,广泛应用在各种C++项目中。
使用glog的时候,查看官方提供的文档(glog-0.4.0/doc/glog.html),我们看到的第一个例子是这样的:
#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";
}
也就是首先调用InitGoogleLogging做初始化工作,然后就可以输出日志了。那么,InitGoogleLogging这个函数做了哪些初始化工作呢?
翻开glog源码(使用的0.4.0版本),可以看到,InitGoogleLogging在logging.h中声明,在logging.cc中,InitGoogleLogging的定义如下:
void InitGoogleLogging(const char* argv0) {
glog_internal_namespace_::InitGoogleLoggingUtilities(argv0);
}
InitGoogleLoggingUtilities接收一个const char*类型的参数,例子中使用argv[0]就是将程序名称作为参数传入。
InitGoogleLoggingUtilities在utilities.h中声明,在utilities.cc中定义如下:
void InitGoogleLoggingUtilities(const char* argv0) {
#ifdef HAVE_STACKTRACE
InstallFailureFunction(&DumpStackTraceAndExit);
#endif
}
InstallFailureFunction的作用是注册一个LOG(FATAL)后的回调函数。在logging.cc中,InstallFailureFunction的定义:
static void logging_fail() {
abort();
}
typedef void (*logging_fail_func_t)() ATTRIBUTE_NORETURN;
logging_fail_func_t g_logging_fail_func = &logging_fail;
void InstallFailureFunction(void (*fail_func)()) {
g_logging_fail_func = (logging_fail_func_t)fail_func;
}
其中,g_logging_fail_func是一个logging_fail_func_t类型的回调函数,默认是调用的logging_fail(),logging_fail()的作用是调用abort()直接终止进程。而我们通过InitGoogleLogging将回调函数设置成了DumpStackTraceAndExit。
下面有两个问题:
1、g_logging_fail_func什么时候调用?
我们从glog的宏定义出发,简单说下g_logging_fail_func什么时候调用的。
glog的入口定义如下:
#define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()
##是宏连接符号,如果severity是INFO,那么LOG(INFO)就是GOOGLE_LOG_INFO.stream()
如果severity是FATAL,那么LOG(FATAL)就是GOOGLE_LOG_FATAL.stream()
查看GOOGLE_LOG_FATAL的定义:
#define COMPACT_GOOGLE_LOG_FATAL google::LogMessageFatal( \
__FILE__, __LINE__)
那么LOG(FATAL)就是google:: LogMessageFatal (__FILE__,__LINE__).stream()
调用一次LOG(FATAL), LogMessageFatal就构造一次,在LogMessageFatal析构时调用Flush()真正写入文件中。
LogMessageFatal的析构函数如下:
LogMessageFatal::~LogMessageFatal() {
Flush();
LogMessage::Fail();
}
Flush()就是真正的写日志这些操作,输出完日志之后,LogMessage::Fail()就会调用上面提到的g_logging_fail_func,函数定义如下:
void LogMessage::Fail() {
g_logging_fail_func();
}
2、DumpStackTraceAndExit做了什么事情?
在utilities.cc中,DumpStackTraceAndExit的定义如下:
static void DumpStackTraceAndExit() {
DumpStackTrace(1, DebugWriteToStderr, NULL);
//省略
abort();
}
也就是这个函数的作用包括两部分:
<1>、打印函数调用堆栈
<2>、调用abort退出
在utilities.cc中,打印函数调用堆栈函数DumpStackTrace的定义如下:
static void DumpStackTrace(int skip_count, DebugWriter *writerfn, void *arg) {
// Print stack trace
void* stack[32];
int depth = GetStackTrace(stack, ARRAYSIZE(stack), skip_count+1);
for (int i = 0; i < depth; i++) {
#if defined(HAVE_SYMBOLIZE)
if (FLAGS_symbolize_stacktrace) {
DumpPCAndSymbol(writerfn, arg, stack[i], " ");
} else {
DumpPC(writerfn, arg, stack[i], " ");
}
#else
DumpPC(writerfn, arg, stack[i], " ");
#endif
}
}
在这个函数中,使用的关键技术包括下面三部分:
<1>、获取stacktrace
在glog中,GetStackTrace的声明在stacktrace.h中,只有GetStackTrace一个方法。GetStackTrace的实现有多个,默认情况下,是使用stacktrace_generic-inl.h中实现的,也就是使用glibc自带的backtrace实现。
int GetStackTrace(void** result, int max_depth, int skip_count) {
static const int kStackLength = 64;
void * stack[kStackLength];
int size;
size = backtrace(stack, kStackLength);
//省略
return result_count;
}
<2>、Symbolize,将stacktrace获取到的信息转化为字符串数组
对于Symbolize,glog没有使用glibc自带的backtrace_symbols,而是自己通过搜索/proc/self/maps查找的,具体实现在symbolize.h/cc。
<3>、demangle,将Symbolize 之后的C++ ABI的标识符转换为C++源程序的标识符,以方便阅读。
对于demangle,glog也没有使用libstdc++的abi::__cxa_demangle(),而是根据Itanium C++ ABI的生成规则,自己解析的,具体实现在demangle.h/cc中。glog中的demangle的实现并不完整,能够解析类名、函数名、构造/析构函数、运算符等,没有解析函数的参数和模板的参数,比如下列跟abi::__cxa_demangle的对比:
使用glibc的backtrace、backtrace_symbols和libstdc++的abi::__cxa_demangle实现的打印函数调用堆栈的代码可以参考这个:https://panthema.net/2008/0901-stacktrace-demangled/
最后,通过一个实际的例子,看看LOG(FATAL)之后,glog打印了哪些东西吧。
#include <glog/logging.h>
void fatalLog() {
LOG(FATAL) << "fatal message";
}
int main(int argc, char** argv){
//glog设置
google::InitGoogleLogging(argv[0]);
fatalLog();
}
输出的调用堆栈为:
而如果删除google::InitGoogleLogging(argv[0])这一句的时候,调用结果如下:
没有调用堆栈信息,程序直接退出了。