文中提到的代码引用自 libwebrtc M90 版本 https://github.com/aggresss/libwebrtc/tree/M90
日志和断言源文件位置:
└── src
└── rtc_base
├── logging.cc
├── logging.h
├── checks.cc
└── checks.h
通常来说中大型的工程都会自己造一些轮子,libwebrtc 也不例外。在 libwebrtc 的源码中出现频率最高的可能是 RTC_CHECK()
和 RTC_LOG()
,类似于 assert()
和 printf()
的功能,来看一下他们的定义:
#define RTC_LOG_FILE_LINE(sev, file, line) \
::rtc::webrtc_logging_impl::LogCall() & \
::rtc::webrtc_logging_impl::LogStreamer<>() \
<< ::rtc::webrtc_logging_impl::LogMetadata(file, line, sev)
#define RTC_LOG(sev) \
!rtc::LogMessage::IsNoop<::rtc::sev>() && \
RTC_LOG_FILE_LINE(::rtc::sev, __FILE__, __LINE__)
#if RTC_CHECK_MSG_ENABLED
#define RTC_CHECK(condition) \
(condition) ? static_cast<void>(0) \
: ::rtc::webrtc_checks_impl::FatalLogCall<false>( \
__FILE__, __LINE__, #condition) & \
::rtc::webrtc_checks_impl::LogStreamer<>()
LogStreamer 为了适配泛型参数使用了 C++11 的 Parameter Pack
特性,增加了阅读难度,所以我们先将这部分拆解出去,假设 LogStreamer 的输入参数只有 std::string
。简化 LogStreamer 的实现流程,只接受 std::string
类型参数,并且取消 <<
操作符的递归操作,只是为了解释 LogStreamer 的工作原理,简化的 LogStreamer 示例如下:
#ifndef LOG_STREAMER_V1_H_
#define LOG_STREAMER_V1_H_
#include <iostream>
#include <string>
#define LOG_V1 \
v1::LogCall(__FILE__, " LOG_V1 ") & v1::LogStreamer()
namespace v1 {
class LogStreamer {
public:
explicit LogStreamer(const char* arg = "")
: log_(arg) {}
LogStreamer operator<<(const char* arg) {
this->log_.append(arg);
return *this;
}
template <typename... U>
void Call(U... args) const {
(std::cout << ... << args) << this->log_ << std::endl;
}
private:
std::string log_;
};
class LogCall final {
public:
LogCall(const char* file, const char* message)
: file_(file), message_(message) {}
void operator&(const LogStreamer& streamer) {
streamer.Call(file_, message_);
}
private:
const char* file_;
const char* message_;
};
} // namespace v1
#endif // LOG_STREAMER_V1_H_
- LogStreamer 通过重载
<<
操作符将每一个需要输出的参数封装为 LogStreamer 后返回,因为<<
操作符的结合顺序是从左到右,所以所有通过<<
结合到一起的参会最后会成为一个由 LogStreamer 组成的逆序单向链表; <<
的优先级高于&
,所以::rtc::webrtc_logging_impl::LogCall()
重载&
操作符后会将上面的 LogStreamer 组成的逆序单向链表作为参数进行输出,它的输出原理是遍历链表中的输出参数,通过模版递归,将链表中的输出参数转化为自己的可变参数,最后通过cstdarg
和fprintf
将输出参数输出。