这里我们以一条最简单的日至输出为例说明:
LOG(WARNING) << "This is a warning message";
这里LOG是一个宏,其定义如下(logging.h line 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.h line 391):
复制代码
1 #if GOOGLE_STRIP_LOG <= 1
2 #define COMPACT_GOOGLE_LOG_WARNING google::LogMessage( \
3 __FILE__, __LINE__, google::GLOG_WARNING)
4 #define LOG_TO_STRING_WARNING(message) google::LogMessage( \
5 __FILE__, __LINE__, google::GLOG_WARNING, message)
6 #else
7 #define COMPACT_GOOGLE_LOG_WARNING google::NullStream()
8 #define LOG_TO_STRING_WARNING(message) google::NullStream()
9 #endif
复制代码
到这里基本就能看出门道了,google::LogMessage和google::NullStream都是类,根据GOOGLE_STRIP_LOG的不同定义,COMPACT_GOOGLE_LOG_ WARNING被定义为LogMessage或者NullStream, NullStream比较简单,从名字上也能测到它就是一个无输出的流(仅仅重载了operator <<,但实际上并不输出任何信息),用以实现某些level的日志信息不被显式输出)。这里主要看LogMessage。
此时根据文件名, 行号, 日志级别构造一个LogMessage类对象(logging.cc line 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() {
return data_->stream_;
}
data_->stream_是一个LogStream对象,其定义如下:
复制代码
1 class GOOGLE_GLOG_DLL_DECL LogStream : public std::ostream {
2 public:
3 LogStream(char *buf, int len, int ctr);
4 //..............此处省略
5 private:
6 base_logging::LogStreamBuf streambuf_;
7 int ctr_; // Counter hack (for the LOG_EVERY_X() macro)
8 LogStream *self_; // Consistency check hack
9 };
复制代码
上面所提及的google::NullStream即是继承自LogStream,所以也是一个std::ostream对象。
至此一个日志输出语句,
LOG(WARNING) << "This is a warning message";
即可以表示为:
google:: LogStream() << "This is a warning message";
到这里就会发现这个和我们熟悉的cout输出是一样的了:
std::cout << "This is a warning message";
一个google:: LogStream对象和std::cout都是std::ostream对象。
从上面也可以看出,每一次输出一条日志信息都要创建一个google::LogMessage对象,在每次输出结束后释放LogMessage对象,在其析构函数中有如下代码:
1 LogMessage::~LogMessage() {
2 Flush();
3 delete allocated_;
4 }
Flush成员函数即是刷新日志缓存区,相当于C++中流操作的flush或者C中文件操作的fflush。另外注意Flush实现里有如下代码:
复制代码
1 //......
2 {
3 MutexLock l(&log_mutex);
4 (this->*(data_->send_method_))();
5 ++num_messages_[static_cast(data_->severity_)];
6 }
7 //......
复制代码
这是为了保证多个日志同时向同一介质进行输出时到保持有序。注意锁的使用前后有{}包围。呵呵,这种用法其实我也偶尔使用,好处就是在一个比较大的语句块中创建一个作用域更小的对象,这样能使该对象及早释放,避免和整个语句块使用同一作用域。比如上面代码中的在加锁时使用了一个更小的作用域,该作用域结束后锁就会立刻释放,而不是等到Flush函数返回时才释放,这样就进一步提高了响应时间(其实这里也有别的做法,比如我之前写的文章:do{...}while(0)的妙用)。
到此一条日志输出就算完成了,其他宏像DLOG、VLOG、VLOG_IF(带条件检测的输出)都是按这种思路展