上一篇从整个工程上简单分析了glog,请看C++的开源跨平台日志库glog学习研究(一),这一篇对glog的实现代码入手,比如在其源码中以宏的使用最为广泛,接下来就先对各种宏的使用做一简单分析。

1. 日志输出宏

这里我们以一条最简单的日至输出为例说明:

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):

 #if GOOGLE_STRIP_LOG <= 1
#define COMPACT_GOOGLE_LOG_WARNING google::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_WARNING google::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.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对象,其定义如下:

 class GOOGLE_GLOG_DLL_DECL LogStream : public std::ostream {
public:
LogStream(char *buf, int len, int ctr);
//..............此处省略
private:
base_logging::LogStreamBuf streambuf_;
int ctr_; // Counter hack (for the LOG_EVERY_X() macro)
LogStream *self_; // Consistency check hack
};

上面所提及的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对象,在其析构函数中有如下代码:

 LogMessage::~LogMessage() {
Flush();
delete allocated_;
}

Flush成员函数即是刷新日志缓存区,相当于C++中流操作的flush或者C中文件操作的fflush。另外注意Flush实现里有如下代码:

 //......
{
MutexLock l(&log_mutex);
(this->*(data_->send_method_))();
++num_messages_[static_cast<int>(data_->severity_)];
}
//......

这是为了保证多个日志同时向同一介质进行输出时到保持有序。注意锁的使用前后有{}包围。呵呵,这种用法其实我也偶尔使用,好处就是在一个比较大的语句块中创建一个作用域更小的对象,这样能使该对象及早释放,避免和整个语句块使用同一作用域。比如上面代码中的在加锁时使用了一个更小的作用域,该作用域结束后锁就会立刻释放,而不是等到Flush函数返回时才释放,这样就进一步提高了响应时间(其实这里也有别的做法,比如我之前写的文章:do{...}while(0)的妙用)。

到此一条日志输出就算完成了,其他宏像DLOG、VLOG、VLOG_IF(带条件检测的输出)都是按这种思路展开的,不再一一介绍了。

2. CHECK_XX宏

我个人感觉这类CHECK_XX宏比上面的LOG宏实现的还要隐晦难懂,当然设计的还是很巧妙的,值得学习一下,我尝试做个分析。

在测试工程的logging_unittest.cc文件line 535的TestCHECK()函数中有如下代码:

 CHECK_NE(, );
CHECK_GE(, );
CHECK_GE(, );
CHECK_LE(, );
CHECK_LE(, );

这些宏从名字上也能猜到是干嘛用的,他们的定义如下:

 #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(, )
------> CHECK_OP(_LE, <=, , )
------> CHECK_OP_LOG(_LE, <=, , , google::LogMessageFatal)
------> #define CHECK_OP_LOG(_LE, <=, 1, 2, google::LogMessageFatal) \
while (std::string * _result = \
google::Check_LEImpl( \
, \
, \
"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> \
inline std::string* name##Impl(const T1& v1, const T2& v2, \
const char* exprtext) { \
if (GOOGLE_PREDICT_TRUE(v1 op v2)) return NULL; \
else return MakeCheckOpString(v1, v2, exprtext); \
} \
inline std::string* name##Impl(int v1, int v2, const char* exprtext) { \
return name##Impl<int, int>(v1, v2, exprtext); \
}

展开后就实现了google::Check_LEImpl函数(其他与此类似,这里只以“<=”为例说明):

CHECK_LE(, ) ------>
while (std::string * _result = google::Check_LEImpl(, , "1 <= 2"))
log(__FILE__, __LINE__,google::CheckOpString(_result)).stream()

其中google::Check_LEImpl又调用了模板实现的Check_LEImpl,该函数根据两个参数v1、v2和操作符op决定了要么返回NULL,要么返回一个string*,如果返回NULL,则不再执行下面的输出,否则则输出日志信息。

其中宏GOOGLE_PREDICT_TRUE、内联函数GetReferenceableValue、函数MakeCheckOpString、函数CheckOpString、结构体CheckOpString都是比较简单的,就不再讲了。

至此,就完成了CHECK_LE(1, 2)的扩展,如果检测为true,则返回NULL,否则就会返回一个有明确提示信息的字符串指针,并输出该信息,然后是程序宕掉。

比如如果验证CHECK_LE(1, 0),因为为false,则触发日志输出:

F0503 ::09.961318   logging_unittest.cc:] Check failed:  <=  ( vs. )
*** Check failure stack trace: ***

然后程序异常退出。

3. 在宏中使用do-while(0)

比如在logging.h的817行有如下宏定义:

 #define CHECK_DOUBLE_EQ(val1, val2)              \
do { \
CHECK_LE((val1), (val2)+.000000000000001L); \
CHECK_GE((val1), (val2)-.000000000000001L); \
} while ()

这里主要关注do-while(0)的使用,恰巧我之前也写过一篇文章介绍do-while(0)的妙用,请看:do{...}while(0)的妙用,这里不再多言了。

4. 使用宏进行全局初始化

请看下面的宏用法:

REGISTER_MODULE_INITIALIZER(utilities, yUserNameInitializer());

REGISTER_MODULE_INITIALIZER在googleinit.h的44行定义:

 #define REGISTER_MODULE_INITIALIZER(name, body)                 \
namespace { \
static void google_init_module_##name () { body; } \
GoogleInitializer google_initializer_module_##name(#name, \
google_init_module_##name); \
}

其中类GoogleInitializer的实现如下:

 class GoogleInitializer {
public:
typedef void (*void_function)(void);
GoogleInitializer(const char*, void_function f) {
f();
}
};

这个比较简单,其实就是做一些比如注册、全局初始化的工作,相应的也可以设置对应的宏用于做程序退出时的清理工作。

C++的开源跨平台日志库glog学习研究(二)--宏的使用的更多相关文章

  1. C++的开源跨平台日志库glog学习研究(三)--杂项

    在前面对glog分别做了两次学习,请看C++的开源跨平台日志库glog学习研究(一).C++的开源跨平台日志库glog学习研究(二)--宏的使用,这篇再做个扫尾工作,算是基本完成了. 编译期断言 动态 ...

  2. C++的开源跨平台日志库glog学习研究(一)

    作为C++领域中为数不多的好用.高效的.跨平台的日志工具,Google的开源日志库glog也算是凤毛麟角了.glog 是一个C++实现的应用级日志记录框架,提供了C++风格的流操作. 恰巧趁着五一我也 ...

  3. DocX开源WORD操作组件的学习系列二

    DocX学习系列 DocX开源WORD操作组件的学习系列一 : http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_sharp_001_docx1.htm ...

  4. 谷歌日志库GLog 使用说明

    1 引用头文件 加载库 #include <glog/include/logging.h> #pragma comment(lib,"libglog.lib") 2 初 ...

  5. Centos7 下谷歌日志库GLog配置

    1 glog下载地址 https://code.google.com/archive/p/google-glog/downloads glog-0.3.3.tar.gz 需要FQ,直接打不开 2 解压 ...

  6. Numpy库的学习(二)

    今天来继续学习一下Numpy库的使用 接着昨天的内容继续 在Numpy中,我们如果想要进行一个判断使用“==” 我们来看下面的代码 vector = np.array([5,10,15,20,25]) ...

  7. glog学习(二):glog主要接口和类分析

    1.glog的主要接口如下. #define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()#define SYSLOG(severity ...

  8. Python Pandas库的学习(二)

    今天我们继续讲下Python中一款数据分析很好的库.Pandas的学习 接着上回讲到的,如果有人听不懂,麻烦去翻阅一下我前面讲到的Pandas学习(一) 如果我们在数据中,想去3,4,5这几行数据,那 ...

  9. asp.net MVC日志插件Log4Net学习笔记二:保存日志到sqlserver的配置

    1.写到sqlserver的配置: <!--保存到SQLSERVER数据库日志--> <log4net> <appender name="AdoNetAppen ...

随机推荐

  1. Wifi小车资料

    下位机代码 #include <avr/wdt.h> #include <SoftwareSerial.h> #include <EEPROM.h> //设置系统启 ...

  2. pyhthon lambda

    lambda x:x+1(1) >>>2 可以这样认为,lambda作为一个表达式,定义了一个匿名函数,上例的代码x为入口参数和出口参数,x+1为函数体,(1)为x的入口初始值, 用 ...

  3. 201709012工作日记--Android消息机制

    1. android的消息机制——Handler机制 参考:http://www.jianshu.com/p/9e4d1fab0f36. Android异步消息处理机制完全解析,带你从源码的角度理解: ...

  4. 20170906工作日记--volley源码的相关方法细节学习

    1. 在StringRequest类中的75行--new String();使用方法 /** * 工作线程将会调用这个方法 * @param response Response from the ne ...

  5. Ansible Ad-Hoc命令

    -a:传入模块的参数,不同的模块要传入的参数不同 -B SECOND:当任务放到后台执行异步任务,设置程序运行的超时时间,传入的是一个数值,单位秒 -C:测试该任务能否正常运行,不对被管理主机做出任何 ...

  6. Oracle财务系统常用标准报表

    http://erpoperator.blog.163.com/blog/static/17899637220111181121616/ Oracle财务系统常用标准报表 总账系统 系统报表名 中文译 ...

  7. Delphi XE4 For IOS中程序的调试(虚拟机,真实机和win32)

    安装完之后,大家可以看一下XE4可以新建的工程类型: File->New: 是不是多出了FireMonkey Mobile Application这一个选项呀! 然后你再点击这个菜单项,弹出Fi ...

  8. Android-AppUtils工具类

    常用APP的工具类,包含版本号.版本名称.安装的应用程序ICON public class AppUtils { private AppUtils(){} /** * 设置状态栏的颜色 * @para ...

  9. Android sdcard文件读写操作

    这次演示以,安卓原生操作系统 Nexus_6手机进行操作: AndroidManifest.xml配置相关权限: <!-- 增加权限 --> <uses-permission and ...

  10. [c# 20问] 4.Console应用获取执行路径

    一行代码可以搞定了~ static void GetAppPath() { string path = System.Reflection.Assembly.GetExecutingAssembly( ...