目录
在上一篇中我们分析完了 CLOG
宏 日志输出 的流程,在结尾的时候我们提出了一个问题:
CLOG(INFO, "default") << "This is a CLOG!";
CLOG
宏的流式输出是如何实现的?今天我们就来解答这个问题。
writer 类的输出运算符
前面我们经过分析知道
CLOG(INFO, "default")
经过展开后相当于:el::base::Writer(el::Level::Info, __FILE__, __LINE__, ELPP_FUNC, el::base::DispatchAction::NormalLog).construct(1, "default")
而
el:: base::Writer
的construct
接口返回的是对象本身即writer
实例,也就是说这里相当于调用的是writer
类的输出运算符,接下来我们看看writer
类的输出运算符都做了什么:template <typename T> inline Writer &operator<<(const T &log) { #if ELPP_LOGGING_ENABLED if (m_proceed) { m_messageBuilder << log; } #endif // ELPP_LOGGING_ENABLED return *this; } inline Writer &operator<<(std::ostream &(*log)(std::ostream &)) { #if ELPP_LOGGING_ENABLED if (m_proceed) { m_messageBuilder << log; } #endif // ELPP_LOGGING_ENABLED return *this; }
writer 类的流操控符
writer
类重载的输出运算符有两个,一个是模板,一个是参数为std:: ostream& (*log)(std:: ostream&)
类型的函数指针。
对于C++流操控符
有所了解的应该知道,我们平时使用std::cout << std::endl;
其中的std::endl
其实就是流操控符,它的类型实际上是一个函数模板的实例,函数模板类型为:template <class _Elem, class _Traits> std::basic_ostream<_Elem, _Traits> &std::endl(std::basic_ostream<_Elem, _Traits> &_Ostr);
而
cout
的类型为std::ostream
,所以上面的模板经过实例化为:std::ostream& std::endl<char, char_traits<char>>(std::ostream& _Ostr);
所以上面的第二个参数类型为
std::ostream& (*log)(std::ostream&)
的输出运算符相当于让writer
拥有了使用流操控符的能力。也就是说我们可以这样使用writer
:el::base::Writer(el::Level::Info, __FILE__, __LINE__, ELPP_FUNC, el::base::DispatchAction::NormalLog).construct(1, "default") << std::endl;
当然我们也可以定制自己的流操控符,这已经相当于输入输出流方面的内容了,与日志库关系不大,限于篇幅这里就不多说了。
再回到上面这两个
writer
的重载的输出运算符上面来,其实现是一样的:#if ELPP_LOGGING_ENABLED // 启用输出日志 if (m_proceed) { // 需要处理这条日志 m_messageBuilder << log; } #endif // ELPP_LOGGING_ENABLED return *this;
这里我们看到
writer
类的输出运算符实际上是委托给m_messageBuilder
的输出运算符来实现的。
el:: base:: MessageBuilder 类
而
m_messageBuilder
的类型为el:: base::MessageBuilder
,前面我们简单的提过,el:: base::MessageBuilder
用于支持各种类型的日志输出。
el:: base::MessageBuilder
重载了各种类型的输出运算符,同时也支持流操控符(如std::endl
)。如:内置类型
、STL
相关类型、QT
相关类型、boost
相关类型、WXWIDGETS
相关类型以及其他类型
的支持。
el:: base::MessageBuilder
所有上述输出运算符实际上做的事情内部都是委托给了m_logger
成员对应的stream
流。通过m_logger
对应的stream
流最终将日志信息暂时保存起来。
el:: base::MessageBuilder
的m_logger
成员是前面writer
实例调用constrcut
接口初始化m_messageBuilder
时( m_messageBuilder.initialize(m_logger); )作为参数传递进去的来,
el:: base:: MessageBuilder
的输出运算符,我们随便看个基本类型的实现就一清二楚了:#define ELPP_SIMPLE_LOG(LOG_TYPE) \ MessageBuilder &operator<<(LOG_TYPE msg) \ { \ m_logger->stream() << msg; \ if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) \ { \ m_logger->stream() << " "; \ } \ return *this; \ } ELPP_SIMPLE_LOG(char)
上面的这个宏
ELPP_SIMPLE_LOG(char)
扩展开为:MessageBuilder &operator<<(char msg) { m_logger->stream() << msg; if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) { m_logger->stream() << " "; } return *this; }
关于
MessageBuilder
的更多的内容后面会作更详细的分析。而
m_logger
的stream
流其实就是base:: type::stringstream_t
其实就是标准库的字符串流.
base:: type:: stringstream_t
实际是个类型别名,定义如下:
下面的宏我省略了多余的部分,只看关键的部分。#if defined(ELPP_UNICODE) typedef std::wstringstream stringstream_t; #else typedef std::stringstream stringstream_t; #endif // defined(ELPP_UNICODE)
上面 CLOG
宏的所有这些分析用一句话总结就是:
writer 对象重载的输出运算符可以将要输出的信息保存到当前写日志对应的日志记录器的字符串流对象中,而 writer 对象 离开作用域时调用的析构函数最终实现了将日志记录器的字符串流对象中保存的这些信息输出到文件或者终端或者其他输出目的地。
CLOG 宏接口调用流程图
CLOG
宏的日志信息保存部分到这里就介绍完了,下一篇我们分析一下前面 CLOG
宏的流程中使用到的其他相关类。