Google glog 使用
Googleglog 库实现了应用级的日志记录,提供了C++ 风格的流操作和各种助手宏。
代码示例:
#include <glog/logging.h>
int _tmain(int argc,_TCHAR* argv[])
{
google::InitGoogleLogging((const char *)argv[0]);
google::SetLogDestination(google::GLOG_INFO, "./myInfo");
LOG(WARNING) << "thisis the 1st warning!";
return 0;
}
“LOG”宏为日志输出关键字,“INFO”为严重性程度。
主要支持功能如下:
1) 参数设置,以命令行启动参数的方式设置标志参数来控制日志记录行为;
2) 严重性分级,根据日志严重性分级记录日志;
3) 可有条件地记录日志信息;
4) 条件中止程序。丰富的条件判定宏,可预设程序终止条件;
5) 异常信号处理。程序异常情况,可自定义异常处理过程;
6) 支持debug功能。可只用于debug模式;
7) 自定义等级日志信息;
8) 原始日志记录。无需加锁与动态分配内存的轻量级线程安全版本;
9) 系统日志记录;
10) google perror风格日志信息;
11) 日志信息移除。
glog的使用是比较简单的,几乎可以不用配置就直接使用了。在配置方式上,glog和一般的日志系统(如log4系列)是不太一样的,后者一般使 用配置文件, 而glog是在命令行参数中指定的。对比优缺点,配置文件做的配置可能更加强大一些, 不过命令行配置虽然简单但是也不失灵活。
glog可通过根据指定的严重性等级,来选择性记录日志。日志信息严重性等级按由低到高排列依次为:INFO,WARNING, ERROR, 和 FATAL四级。使用者可以在命令行中设置严重性等级门限值来控制日志的输出,详细见“参数设置”部分的“minloglevel”标志值的介绍。
其中FATAL等级的日志会在记录以后终止程序运行,要谨慎使用。
这里我们以一条最简单的日至输出为例说明:
LOG(WARNING) << "Thisis a warning message";
这里LOG是一个宏,其定义如下(logging.hline 487):
#define LOG(severity) COMPACT_GOOGLE_LOG_ ##severity.stream()
这里根据LOG宏中的severity的不同有分别扩展成了另外四个宏,其中severity有四个预定义(log_severity.h line 51-59),分别代表不同级别的日志输出,有INFO、WARNING、ERROR、FATAL,以WARNING为例,LOG(WARNING)被扩 展为COMPACT_GOOGLE_LOG_WARNING.stream()。其中COMPACT_GOOGLE_LOG_WARNING又是另外一个 宏(logging.hline 391):
#if GOOGLE_STRIP_LOG <= 1
#define COMPACT_GOOGLE_LOG_WARNINGgoogle::LogMessage( \
__FILE__, __LINE__, google::GLOG_WARNING)
#define LOG_TO_STRING_WARNING(message)google::LogMessage( \
__FILE__, __LINE__, google::GLOG_WARNING, message)
#else
#define COMPACT_GOOGLE_LOG_WARNINGgoogle::NullStream()
#define LOG_TO_STRING_WARNING(message)google::NullStream()
#endif
到这里基本就能看出门道了,google::LogMessage和google::NullStream都是类,根据 GOOGLE_STRIP_LOG的不同定义,COMPACT_GOOGLE_LOG_WARNING被定义为LogMessage或者 NullStream,NullStream比较简单,从名字上也能测到它就是一个无输出的流(仅仅重载了operator<<,但实际上并 不输出任何信息),用以实现某些level的日志信息不被显式输出)。这里主要看LogMessage。
此时根据文件名,行号, 日志级别构造一个LogMessage类对象(logging.ccline 1153):
LogMessage::LogMessage(const char* file, int line,LogSeverity severity) :allocated_(NULL)
{
Init(file, line,severity, &LogMessage::SendToLog);
}
LogMessage有很多重载构造,这里不再一一列举了。注意构造里的初始化函数Init,除了文件名,行号, 日志级别,还多了一个参数,Init声明如下:
void Init(const char* file, int line,LogSeverity severity, void (LogMessage::*send_method)());
即最后一个参数是一个函数指针,且可配置,用以设置真正的日志输出,比如输出到文件、控制台等,甚至有可能配置成输出到远程网络端。Init内部用以初始化日志输入的流缓冲区,初始化日志创建时间,格式,确定打印日志文件名等等。
此时一个完整的LogMessage的对象就创建并初始化完成了,回到LOG(severity)宏定义处,此时LOG宏可以表示成如下定义:
#define LOG(severity) google::LogMessage().stream()
也即是最终被展开为google::LogMessage类的成员函数stream()的返回值,stream()实现如下:
std::ostream&LogMessage::stream()
{
returndata_->stream_;
}
data_->stream_是一个LogStream对象,其定义如下:
class GOOGLE_GLOG_DLL_DECL LogStream : public std::ostream
{
public:
LogStream(char *buf, int len, int ctr);
//..............此处省略
private:
base_logging::LogStreamBufstreambuf_;
int ctr_; // Counter hack (for theLOG_EVERY_X() macro)
LogStream *self_; // Consistency check hack
};
上面所提及的google::NullStream即是继承自LogStream,所以也是一个std::ostream对象。
至此一个日志输出语句,
LOG(WARNING) << "Thisis a warning message";
即可以表示为:
google:: LogStream() << "Thisis a warning message";
到这里就会发现这个和我们熟悉的cout输出是一样的了:
std::cout << "Thisis a warning message";
一个google::LogStream对象和std::cout都是std::ostream对象。
从上面也可以看出,每一次输出一条日志信息都要创建一个google::LogMessage对象,在每次输出结束后释放LogMessage对象,在其析构函数中有如下代码:
LogMessage::~LogMessage() {
Flush();
deleteallocated_;
}
Flush成员函数即是刷新日志缓存区,相当于C++中流操作的flush或者C中文件操作的fflush。另外注意Flush实现里有如下代码:
//......
{
MutexLockl(&log_mutex);
(this->*(data_->send_method_))();
++num_messages_[static_cast<int>(data_->severity_)];
}
//......
这是为了保证多个日志同时向同一介质进行输出时到保持有序。注意锁的使用前后有{}包围。呵呵,这种用法其实我也偶尔使用,好处就是在一个比较大的 语句块中创建一个作用域更小的对象,这样能使该对象及早释放,避免和整个语句块使用同一作用域。比如上面代码中的在加锁时使用了一个更小的作用域,该作用 域结束后锁就会立刻释放,而不是等到Flush函数返回时才释放,这样就进一步提高了响应时间。
LOG_IF(INFO,num_cookies > 10) << "Gotlots of cookies"; //当条件满足时输出日志
LOG_EVERY_N(INFO, 10) << "Gotthe " << google::COUNTER<< "thcookie"; //google::COUNTER记录该语句被执行次数,从1开始,在第一次运行输出日志之后,每隔 10 次再输出一次日志信息
LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Gotthe " << google::COUNTER<< "th bigcookie"; //上述两者的结合,不过要注意,是先每隔 10 次去判断条件是否满足,如果滞则输出日志;而不是当满足某条件的情况下,每隔 10 次输出一次日志信息。
LOG_FIRST_N(INFO, 20) << "Gotthe " << google::COUNTER<< "thcookie"; //当此语句执行的前 20 次都输出日志,然后不再输出
演示代码如下:
#include <glog/logging.h>
int main(int argc,char* argv[])
{
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold=google::INFO;
FLAGS_colorlogtostderr=true;
for(int i = 1; i <= 100;i++)
{
LOG_IF(INFO,i==100)<<"LOG_IF(INFO,i==100) google::COUNTER="<<google::COUNTER<<" i="<<i;
LOG_EVERY_N(INFO,10)<<"LOG_EVERY_N(INFO,10) google::COUNTER="<<google::COUNTER<<" i="<<i;
LOG_IF_EVERY_N(WARNING,(i>50),10)<<"LOG_IF_EVERY_N(INFO,(i>50),10) google::COUNTER="<<google::COUNTER<<" i="<<i;
LOG_FIRST_N(ERROR,5)<<"LOG_FIRST_N(INFO,5) google::COUNTER="<<google::COUNTER<<" i="<<i;
}
google::ShutdownGoogleLogging();
}
LOG //内置日志
VLOG //自定义日志
DLOG //DEBUG模式可输出的日志
DVLOG //DEBUG模式可输出的自定义日志
SYSLOG //系统日志,同时通过 syslog() 函数写入到/var/log/message 文件
PLOG //perror风格日志,设置errno状态并输出到日志中
RAW_LOG //线程安全的日志,需要#include <glog/raw_logging.h>
前六种的日志使用方法完全相同(包括条件日志输出),而RAW_LOG 使用方法比较特殊,且不支持条件日志输出,另外不接受colorlogtostderr 的颜色设置。自定义日志也不接受 colorlogtostderr的颜色设置,另外其日志严重级别也为自定义数字,且与默认日志严重级别相反,数字越小 严重级别越高。如:
#include <glog/logging.h>
#include <glog/raw_logging.h>
class GLogHelper
{
public:
GLogHelper(char*program)
{
google::InitGoogleLogging(program);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
FLAGS_v = 3;
}
~GLogHelper()
{
google::ShutdownGoogleLogging();
}
};
int main(int argc,char* argv[])
{
GLogHelpergh(argv[0]);
LOG(ERROR) <<"LOG";
VLOG(3) <<"VLOG";
DLOG(ERROR) <<"DLOG";
DVLOG(3) <<"DVLOG";
SYSLOG(ERROR) <<"SYSLOG";
PLOG(ERROR) <<"PLOG";
RAW_LOG(ERROR,"RAW_LOG");
}
当通过该宏指定的条件不成立的时候,程序会中止,并且记录对应的日志信息。功能类似于ASSERT,区别是 CHECK 宏不受 NDEBUG 约束,在 release 版中同样有效。
我个人感觉这类CHECK_XX宏比上面的LOG宏实现的还要隐晦难懂,当然设计的还是很巧妙的,值得学习一下,我尝试做个分析。
在测试工程的logging_unittest.cc文件line535的TestCHECK()函数中有如下代码:
CHECK_NE(1, 2);
CHECK_GE(1, 1);
CHECK_GE(2, 1);
CHECK_LE(1, 1);
CHECK_LE(1, 2);
定义如下:
#define CHECK_EQ(val1, val2) CHECK_OP(_EQ, ==,val1, val2)
#define CHECK_NE(val1, val2) CHECK_OP(_NE, !=,val1, val2)
#define CHECK_LE(val1, val2) CHECK_OP(_LE, <=,val1, val2)
#define CHECK_LT(val1, val2) CHECK_OP(_LT, < ,val1, val2)
#define CHECK_GE(val1, val2) CHECK_OP(_GE, >=,val1, val2)
#define CHECK_GT(val1, val2) CHECK_OP(_GT, > ,val1, val2)
其中CHECK_OP宏定义如下:
#define CHECK_OP(name, op, val1, val2) \
CHECK_OP_LOG(name, op, val1, val2, google::LogMessageFatal)
而CHECK_OP_LOG宏定义如下:
typedef std::string_Check_string;
#define CHECK_OP_LOG(name, op, val1, val2,log) \
while(google::_Check_string* _result = \
google::Check##name##Impl( \
google::GetReferenceableValue(val1), \
google::GetReferenceableValue(val2), \
#val1 " " #op " " #val2)) \
log(__FILE__, __LINE__, \
google::CheckOpString(_result)).stream()
接下来我们以CHECK_LE(1,2);为例,将其逐步扩展:
CHECK_LE(1, 2)
------>CHECK_OP(_LE, <=, 1, 2)
------>CHECK_OP_LOG(_LE, <=, 1, 2,google::LogMessageFatal)
------>#define CHECK_OP_LOG(_LE, <=, 1, 2,google::LogMessageFatal) \
while (std::string* _result = \
google::Check_LEImpl( \
1, \
2, \
"1<= 2")) \
log(__FILE__,__LINE__, \
google::CheckOpString(_result)).stream()
其中google::Check_LEImpl也是通过宏预先实现的,这个宏就是DEFINE_CHECK_OP_IMPL(Check_LE,<=):
#define DEFINE_CHECK_OP_IMPL(name, op) \
template<typename T1, typename T2> \
inlinestd::string* name##Impl(const T1& v1, const T2& v2, \
const char*exprtext) { \
if(GOOGLE_PREDICT_TRUE(v1 op v2)) return NULL; \
elsereturn MakeCheckOpString(v1, v2, exprtext); \
} \
inlinestd::string* name##Impl(int v1, int v2, const char* exprtext) { \
returnname##Impl<int, int>(v1, v2, exprtext); \
}
展开后就实现了google::Check_LEImpl函数(其他与此类似,这里只以“<=”为例说明):
CHECK_LE(1, 2) ------>
while (std::string* _result =google::Check_LEImpl(1, 2, "1<= 2"))
log(__FILE__,__LINE__,google::CheckOpString(_result)).stream()
其中google::Check_LEImpl又调用了模板实现的Check_LEImpl,该函数根据两个参数v1、v2和操作符op决定了要么返回NULL,要么返回一个string*,如果返回NULL,则不再执行下面的输出,否则则输出日志信息。
至此,就完成了CHECK_LE(1,2)的扩展,如果检测为true,则返回NULL,否则就会返回一个有明确提示信息的字符串指针,并输出该信息,然后是程序宕掉。
通过 google::InstallFailureSignalHandler();即可注册,将 coredumped 信息输出到 stderr,如:
#include <glog/logging.h>
#include <string>
#include <fstream>
//将信息输出到单独的文件和LOG(ERROR)
void SignalHandle(const char* data, int size)
{
std::ofstreamfs("glog_dump.log",std::ios::app);
std::stringstr = std::string(data,size);
fs<<str;
fs.close();
LOG(ERROR)<<str;
}
class GLogHelper
{
public:
GLogHelper(char*program)
{
google::InitGoogleLogging(program);
FLAGS_colorlogtostderr=true;
google::InstallFailureSignalHandler();
//默认捕捉 SIGSEGV 信号信息输出会输出到stderr,可以通过下面的方法自定义输出方式:
google::InstallFailureWriter(&SignalHandle);
}
~GLogHelper()
{
google::ShutdownGoogleLogging();
}
};
void fun()
{
int* pi = new int;
delete pi;
pi = 0;
int j = *pi;
}
int main(int argc,char* argv[])
{
GLogHelpergh(argv[0]);
fun();
}
如果不使用 google::InstallFailureSignalHandler(); 则只会输出“段错误” 三个字,难于排查。
google::SetLogDestination(google::ERROR,"log/prefix_"); //第一个参数为日志级别,第二个参数表示输出目录及日志文件名前缀。
google::SetStderrLogging(google::INFO); //输出到标准输出的时候大于 INFO级别的都输出;等同于 FLAGS_stderrthreshold=google::INFO;
FLAGS_logbufsecs =0; //实时输出日志
FLAGS_max_log_size =100; //最大日志大小(MB)
#define GOOGLE_STRIP_LOG 3 // 小于此级别的日志语句将在编译时清除,以减小编译后的文件大小,必须放在#include 前面才有效。
如果可执行文件名为"test",则将日志输出到文件后,还会生成 test.ERROR,test.WARNING,test.INFO三个链接文件,分别链接到对应级别的日志文件。如果日志输出超过 FLAGS_max_log_size 设置的大小,则会分为多个文件存储,链接文件就会指向其中最新的对应级别的日志文件。所以当日志文件较多时,查看链接文件来查看最新日志挺方便的。
glog默认是根据进程ID是否改变和文件大小是否超过预定值来确定是否需要新建日志文件的,此处可以参考glog源码logging.cc 文件中的 voidLogFileObject::Write 函数中
if (static_cast<int>(file_length_>> 20) >=MaxLogSize() ||
PidHasChanged()) {
我们只需要在此处加一个日期判断就可以了,PidHasChanged()定义于utilities.cc 文件中,可以加一个类似的DayHasChanged() 函数(注意 utilities.h文件中加上函数声明):
static int32 g_main_day = 0;
bool DayHasChanged()
{
time_traw_time;
struct tm*tm_info;
time(&raw_time);
tm_info =localtime(&raw_time);
if (tm_info->tm_mday!= g_main_day)
{
g_main_day = tm_info->tm_mday;
return true;
}
return false;
}
再修改 voidLogFileObject::Write 函数中的判断条件即可:
if (static_cast<int>(file_length_>> 20) >=MaxLogSize() ||
PidHasChanged() ||DayHasChanged()) {