zoukankan      html  css  js  c++  java
  • glog简单分析

    codedump » glog简单分析

    glog简单分析

    2010年12月22日 那谁 发表评论 阅读评论

    项目组一直使用google的glog开源库进行日志输出, 花时间研究了一下, 做些分享.

    这里就不分析它的使用方式了, 还是比较简单的, 几乎可以不用配置就直接使用了.另外, 如果真的需要配置的话, glog和一般的日志系统(如log4系列)是不太一样的, 后者一般使用配置文件, 而glog是在命令行参数中指定的.对比优缺点, 配置文件做的配置可能更加强大一些, 不过命令行配置虽然简单但是也不失灵活.具体使用方式还是自己去看看吧:)之所以这里会写这篇文章是因为看到一些同学说glog用了大量的宏技巧, 看得晕, 其实仔细看并不复杂,另外,它的实现也比较精巧,用过了那些很”重”的log库之外,不失为另一个好的参考.

    1) 一般的日志输出流程
    以一个日志输出的完整流程来做说明吧.
    glog一般使用VLOG(num)或者LOG(severity)两种形式的宏进行输出.就以LOG(severity)为例进行说明, VLOG(num)系列应该类似了.
    比如要输出一条日志信息, 如
    LOG(ERROR) << "hello world"

    LOG宏的定义是:

    1#define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()

    可以看到它将根据具体的名字展开为另一个宏,因为上面选择的输出级别是ERROR, 所以展开的名称为COMPACT_GOOGLE_LOG_ERROR

    继续跟进这个宏的定义:

    1#define COMPACT_GOOGLE_LOG_ERROR @ac_google_namespace@::LogMessage( \
    2__FILE__, __LINE__, @ac_google_namespace@::ERROR)

    可以看到这个宏的作用其实就是创建了一个名为LogMessage的类, 构造函数的输入参数包括了:文件名, 行号, 日志级别

    继续跟进LogMessage的构造函数:

    1LogMessage::LogMessage(const char* file, int line, LogSeverity severity) {
    2Init(file, line, severity, &LogMessage::SendToLog);
    3}

    这一次, 多了一个参数, 名为SendToLog的类成员函数指针.这里需要说明的是, 其实这个函数指针参数的目的是进行日志输出的操作,但是是可以配置的,因为LogMessage还有另外重载的构造函数其中有这个参数的,只是这里说明的是最简单的情况,所以就考虑了这个函数指针默认为SendToLog的情况,从名字可以猜测到,该函数的作用是向磁盘进行日志输出操作,另外glog中还可以向标准输出进行输出,如果有必要,应该还可以通过网络对某个地址端口进行输出–这些情况我都没有进一步跟进了,只想说明该函数指针的作用是向不同的介质输出日志,而使用函数指针作为参数就是为了让这个行为可以配置.

    继续往下走,看看LogMessage::Init函数做的事情.这里不贴代码了,有兴趣的可以自己跟进看看.简单来说做的事情是:初始化日志输入的流缓冲区,初始化该日志的时间,格式,找到日志打印的文件名等.

    好了,至此,一个完整的LogMessage就创建完毕.可以看到,在glog中,任何的一条日志信息,最终都会对应到一个新创建的LogMessage对象,有什么具体的好处呢,后面会分析到.

    上面的日志输出流程,其实还没有完,因为还要进行流输入操作呢,就是输入”hello world”字符串.没错,退回头看看LOG宏的定义

    1#define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()

    如果说, 前面的COMPACT_GOOGLE_LOG_ ## severity创建了一个LogMessage类对象,那么其实这个宏最终的结果是返回这个LogMessage类对象的成员stream(), 在提到LogMessage::Init函数的时候就提到过,该函数初始化了流输入缓冲区.

    来看LogMessage类中该流输入类的定义:

    01class GOOGLE_GLOG_DLL_DECL LogStream : public std::ostrstream {
    02#ifdef _MSC_VER
    03# pragma warning(default: 4275)
    04#endif
    05public:
    06LogStream(char *buf, int len, int ctr)
    07: ostrstream(buf, len),
    08ctr_(ctr) {
    09self_ = this;
    10}
    11int ctr() const { return ctr_; }
    12void set_ctr(int ctr) { ctr_ = ctr; }
    13LogStream* self() const { return self_; }
    14private:
    15int ctr_;  // Counter hack (for the LOG_EVERY_X() macro)
    16LogStream *self_;  // Consistency check hack
    17};

    其实很简单, 从std::ostrstream中继承过来,构造函数中有一个缓冲区就好了.在LogMessage::Init类中,定义该缓冲区的大小为:

    1buf_ = new char[kMaxLogMessageLen+1];

    其中

    1const size_t LogMessage::kMaxLogMessageLen = 30000;

    这里可以看到glog限制的一条日志长度大小为30000 byte.
    其实我不明白为什么这样做, 如果每次打印一条日志都要分配一个这么大的缓冲区,而其实很多时候都是用不了这么大的,岂不是浪费了,难道说一次过分配足够大的内存可以有效率的提高,回头要尝试修改一下这个做法,不从std::ostrstream中继承自己管理缓冲区了,而是直接使用标准的std::ostringstream看看效率有没有大的变化.

    好了,到此为止,流输入也有了,尽管往里面写数据就好.那么什么时候进行输出呢?我在今年早些时候,也曾经因为这个问题批判过C++, 由于对流输入操作结束位置判断方式的缺失,glog采用的是在这个创建的临时LogMessage对象(说它是”临时对象”是因为它是匿名的,因此不会保存,创建了就会被释放)被释放的时候,在析构函数中做输出操作:

    1LogMessage::~LogMessage() {
    2Flush();
    3delete allocated_;
    4}

    Flush函数不详细分析了,主要做的事情就是将日志和之前得到的日期等进行格式化,最后调用注册的输出日志用的函数指针进行输出操作.

    到此为止,一条glog日志就输出完成了.
    再回头看看,实际上这里应该还有一些东西是需要全局使用的,比如有多条日志同时向一个文件进行输出的时候,需要对文件进行加锁,还比如要更新一些统计的数据,如每个级别的日志都有多少条了.这些在glog中都是全局变量:

    1static Mutex log_mutex;
    2// Number of messages sent at each severity.  Under log_mutex.
    3int64 LogMessage::num_messages_[NUM_SEVERITIES] = {0, 0, 0, 0};

    当然,这里的num_messages_数组准确的说是LogMessage的静态成员变量,但是大体理解为全局变量也不为错,因为这个数据全局都只有一份了.

    我自己以前也曾经做过日志系统,我的做法将这个系统作为一个Singleton类, 因为对绝大多数的系统而言, 日志输出系统都应该只有一份,然后使用这个单件进行操作.glog没有这样做,每条日志都是一个单独的LogMessage类对象,相互之间的影响不太多,其他的全局的资源都是全局对象.个人的分析,由于C++对单件类的实现支持很难,目前尚没有找到一个完全称得上高效而且绝对安全的实现方式(如果有,请告知,什么double check之类的就不要说了:),所以glog这种方式看上来精巧些.

    2) CHECK_*宏的实现
    glog有另一个强大的功能,也是很精巧,就是CHECK_*宏,它可以检测各种情况比如相等,大于小于等.这些功能其实很常见了,但是为什么说它精巧呢,看分析吧.
    先来看这些宏的定义

    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) #define CHECK_GE(val1, val2) CHECK_OP(_GE, >=, val1, val2)
    5#define CHECK_GT(val1, val2) CHECK_OP(_GT, > , val1, val2)

    可以看到, 最后都会走到CHECK_OP这个宏里面:

    1#define CHECK_OP(name, op, val1, val2) \
    2CHECK_OP_LOG(name, op, val1, val2, @ac_google_namespace@::LogMessageFatal)

    接着跟进CHECK_OP_LOG宏:

    1#define CHECK_OP_LOG(name, op, val1, val2, log)                         \
    2while (@ac_google_namespace@::_Check_string* _result =                \
    3@ac_google_namespace@::Check##name##Impl(                      \
    4@ac_google_namespace@::GetReferenceableValue(val1),        \
    5@ac_google_namespace@::GetReferenceableValue(val2),        \
    6#val1 " " #op " " #val2))                                  \
    7log(__FILE__, __LINE__,                                             \
    8@ac_google_namespace@::CheckOpString(_result)).stream()

    可以看到, 这个宏的作用是判断某个条件,满足该条件则输出一条日志,log参数在这里对应的LogMessageFatal, 这个函数是输出一条信息之后让系统core掉.而Check##name##Impl这个宏,是根据不同的判断条件具体生成一个宏,如果是CHECK_EQ的话,对应的就是:

    1DEFINE_CHECK_OP_IMPL(_EQ, ==)

    其中:

    01#define DEFINE_CHECK_OP_IMPL(name, op) \
    02template  \
    03inline std::string* Check##name##Impl(const t1& v1, const t2& v2, \
    04const char* names) { \
    05if (v1 op v2) return NULL; \
    06else return MakeCheckOpString(v1, v2, names); \
    07} \
    08inline std::string* Check##name##Impl(int v1, int v2, const char* names) { \
    09return Check##name##Impl(v1, v2, names); \
    10}

    好了,终于到重点了,上面的代码中,关键的几句是:

    1if (v1 op v2) return NULL; \
    2else return MakeCheckOpString(v1, v2, names); \

    根据前面的情况, 如果v1 op v2为true, 则返回NULL;否则返回一个字符串, 从而调用log进行输出然后让系统core掉.
    来看MakeCheckOpString的定义:

    01template
    02std::string* MakeCheckOpString(const t1& v1, const t2& v2, const char* names) {
    03// It means that we cannot use stl_logging if compiler doesn't
    04// support using expression for operator.
    05// TODO(hamaji): Figure out a way to fix.
    06#if @ac_cv_cxx_using_operator@
    07using ::operator<<;
    08#endif
    09std::strstream ss;
    10ss << names << " (" << v1 << " vs. " << v2 << ")";
    11return new std::string(ss.str(), ss.pcount());
    12}

    好了, 其实MakeCheckOpString的作用很简单啊,就是在CHECK_*检查失败的时候给出一条相对提示友好的输出信息罢了, 比如写CHECK_EQ(1, 2)的时候,这个CHECK显然是失败的,那么它给出的失败信息就是
    “1 == 2 (1 vs. 2)”
    其中”1 == 2″这个字符串对应的是

    1#define CHECK_OP_LOG(name, op, val1, val2, log)                         \
    2while (@ac_google_namespace@::_Check_string* _result =                \
    3@ac_google_namespace@::Check##name##Impl(                      \
    4@ac_google_namespace@::GetReferenceableValue(val1),        \
    5@ac_google_namespace@::GetReferenceableValue(val2),        \
    6#val1 " " #op " " #val2))                                  \
    7log(__FILE__, __LINE__,                                             \
    8@ac_google_namespace@::CheckOpString(_result)).stream()

    中的”#val1 ” ” #op ” ” #val2″

    分析完了,不得不说,glog中使用宏的地方确实多,精巧但是不仔细看看还是会看得很晕的,但是看明白了之后还是会赞一下作者的做法.

    glog的功能不止这些,这两个部分只是我目前关注到的部分,以后可能还会做别的模块分析.

  • 相关阅读:
    迭代器相关整理
    闭包的功能举例
    函数参数相关整理
    python中进制转换及IP地址转换
    dD Geometry Kernel ( Geometry Kernels) CGAL 4.13 -User Manual
    2D and 3D Linear Geometry Kernel ( Geometry Kernels) CGAL 4.13 -User Manual
    Monotone and Sorted Matrix Search ( Arithmetic and Algebra) CGAL 4.13 -User Manual
    Algebraic Kernel ( Arithmetic and Algebra) CGAL 4.13 -User Manual
    数论中的环概念
    QT的配置及目录结构
  • 原文地址:https://www.cnblogs.com/lexus/p/2872279.html
Copyright © 2011-2022 走看看