• C++的开源跨平台日志库glog学习研究(二)--宏的使用


    上一篇从整个工程上简单分析了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):

    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<int>(data_->severity_)];
    6   }
    7 //......

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

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

    2. CHECK_XX宏

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

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

    1 CHECK_NE(1, 2);
    2 CHECK_GE(1, 1);
    3 CHECK_GE(2, 1);
    4 CHECK_LE(1, 1);
    5 CHECK_LE(1, 2);

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

    1 #define CHECK_EQ(val1, val2) CHECK_OP(_EQ, ==, val1, val2)
    2 #define CHECK_NE(val1, val2) CHECK_OP(_NE, !=, val1, val2)
    3 #define CHECK_LE(val1, val2) CHECK_OP(_LE, <=, val1, val2)
    4 #define CHECK_LT(val1, val2) CHECK_OP(_LT, < , val1, val2)
    5 #define CHECK_GE(val1, val2) CHECK_OP(_GE, >=, val1, val2)
    6 #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宏定义如下:

    1 typedef std::string _Check_string;
    2 #define CHECK_OP_LOG(name, op, val1, val2, log)          
    3   while (google::_Check_string* _result =                
    4          google::Check##name##Impl(                      
    5              google::GetReferenceableValue(val1),        
    6              google::GetReferenceableValue(val2),        
    7              #val1 " " #op " " #val2))                   
    8     log(__FILE__, __LINE__,                              
    9         google::CheckOpString(_result)).stream()

    接下来我们以CHECK_LE(1, 2);为例,将其逐步扩展:

     1  CHECK_LE(1, 2)  
     2  ------> CHECK_OP(_LE, <=, 1, 2)  
     3  ------> CHECK_OP_LOG(_LE, <=, 1, 2, google::LogMessageFatal) 
     4  ------> #define CHECK_OP_LOG(_LE, <=, 1, 2, google::LogMessageFatal)                         
     5   while (std::string * _result =  
     6          google::Check_LEImpl(    
     7              1,                   
     8              2,                   
     9              "1 <= 2"))           
    10     log(__FILE__, __LINE__,       
    11         google::CheckOpString(_result)).stream()

    其中google::Check_LEImpl也是通过宏预先实现的,这个宏就是DEFINE_CHECK_OP_IMPL(Check_LE, <=):

     1 #define DEFINE_CHECK_OP_IMPL(name, op) 
     2   template <typename T1, typename T2>  
     3   inline std::string* name##Impl(const T1& v1, const T2& v2,    
     4                             const char* exprtext) {  
     5     if (GOOGLE_PREDICT_TRUE(v1 op v2)) return NULL;  
     6     else return MakeCheckOpString(v1, v2, exprtext); 
     7   } 
     8   inline std::string* name##Impl(int v1, int v2, const char* exprtext) { 
     9     return name##Impl<int, int>(v1, v2, exprtext); 
    10   }

    展开后就实现了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,则不再执行下面的输出,否则则输出日志信息。

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

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

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

    F0503 17:39:09.961318  4232 logging_unittest.cc:203] Check failed: 1 <= 0 (1 vs. 0)
    *** Check failure stack trace: ***

    然后程序异常退出。

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

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

    1 #define CHECK_DOUBLE_EQ(val1, val2)              
    2   do {                                           
    3     CHECK_LE((val1), (val2)+0.000000000000001L); 
    4     CHECK_GE((val1), (val2)-0.000000000000001L); 
    5   } while (0)

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

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

    请看下面的宏用法:

    REGISTER_MODULE_INITIALIZER(utilities, yUserNameInitializer());

    REGISTER_MODULE_INITIALIZER在googleinit.h的44行定义:

    1 #define REGISTER_MODULE_INITIALIZER(name, body)                 
    2   namespace {                                                   
    3     static void google_init_module_##name () { body; }          
    4     GoogleInitializer google_initializer_module_##name(#name,   
    5             google_init_module_##name);                         
    6   }

    其中类GoogleInitializer的实现如下:

    1 class GoogleInitializer {
    2  public:
    3   typedef void (*void_function)(void);
    4   GoogleInitializer(const char*, void_function f) {
    5     f();
    6   }
    7 };

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

  • 相关阅读:
    【vue】vue +element 搭建项目,vue-cli 如何打包上线
    【移动端】单位em相关资料
    管道 |、|&、tee
    重定向
    Bash快捷键
    man 与 help
    linux磁盘分区、格式化、挂载
    目录(cd mkdir rmdir rm pwd ls) 文件(ln touch mv rm cat more head rail) 文件权限(chmod chown chgrp) 文件通配符(* ? [])
    用户环境变量 shell变量 别名
    用户、组和身份认证
  • 原文地址:https://www.cnblogs.com/lizhenghn/p/3705410.html
Copyright © 2020-2023  润新知