• 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的功能不止这些,这两个部分只是我目前关注到的部分,以后可能还会做别的模块分析.

  • 相关阅读:
    Codeforces 1045C Hyperspace Highways (看题解) 圆方树
    Codeforces 316E3 线段树 + 斐波那切数列 (看题解)
    Codeforces 803G Periodic RMQ Problem 线段树
    Codeforces 420D Cup Trick 平衡树
    Codeforces 295E Yaroslav and Points 线段树
    Codeforces 196E Opening Portals MST (看题解)
    Codeforces 653F Paper task SA
    Codeforces 542A Place Your Ad Here
    python基础 异常与返回
    mongodb 删除
  • 原文地址:https://www.cnblogs.com/lexus/p/2872279.html
Copyright © 2020-2023  润新知