WebRTC 的 log 系統實現分析
WebRTC 有多套 log 輸出系統,一套位於 webrtc/src/base
下,包括 webrtc/src/base/logging.h
和 webrtc/src/base/logging.cc
等文件,主要的 class 位於 namespace logging
中。另一套位於 webrtc/src/rtc_base
下,包括 webrtc/src/rtc_base/logging.h
和 webrtc/src/rtc_base/logging.cc
等文件,主要的 class 位於 namespace rtc
中。本文主要分析 namespace rtc
中的這套 log 輸出系統。它可以非常方便地輸出類似於下面這種格式的 log:
(bitrate_prober.cc:64): Bandwidth probing enabled, set to inactive
(rtp_transport_controller_send.cc:45): Using TaskQueue based SSCC
(paced_sender.cc:362): ProcessThreadAttached 0x0x7fb2e813c0d0
log 中包含了輸出 log 的具體文件位置,和特定的要輸出的 log 內容信息。
整體來看,這個 log 系統可以分爲四層:對於 log 系統的用戶,看到的是用於輸出不同重要性的 log 的一組宏,如 RTC_LOG(sev)
,這組宏提供了類似於輸出流 std::ostream
的接口來輸出 log;log 系統的核心是 LogMessage
,它用於對要輸出的 log 做格式化,產生最終要輸出的字符串,並把產生的字符串送給 log 後端完成 log 輸出,同時還可以通過它對 log 系統的行爲施加控制;最下面的是 log 後端,log 後端用 LogSink
接口描述;在 RTC_LOG(sev)
等宏和 LogMessage
之間的是用於實現 log 系統的用戶看到的類似於輸出流的接口的模板類 LogStreamer
和 LogCall
等。這個 log 系統整體如下圖所示:
Log 後端
Log 後端用 LogSink
接口描述,這個接口聲明(位於 webrtc/src/rtc_base/logging.h
文件中)如下:
// Virtual sink interface that can receive log messages.
class LogSink {
public:
LogSink() {}
virtual ~LogSink() {}
virtual void OnLogMessage(const std::string& msg,
LoggingSeverity severity,
const char* tag);
virtual void OnLogMessage(const std::string& message,
LoggingSeverity severity);
virtual void OnLogMessage(const std::string& message) = 0;
};
LogSink
用於從 LogMessage
接收不同 severity 的 log 消息。LogSink
的兩個非純虛函數實現如下(位於 webrtc/src/rtc_base/logging.cc
):
// Inefficient default implementation, override is recommended.
void LogSink::OnLogMessage(const std::string& msg,
LoggingSeverity severity,
const char* tag) {
OnLogMessage(tag + (": " + msg), severity);
}
void LogSink::OnLogMessage(const std::string& msg,
LoggingSeverity /* severity */) {
OnLogMessage(msg);
}
LogMessage
log 系統的全局控制
LogMessage
可以用於對 log 系統施加一些全局性的控制,這主要是指如下這些全局性的狀態:
- log 輸出的最低 severity,通過
g_min_sev
控制; - debug 輸出的最低 severity,通過
g_dbg_sev
控制; - 是否要向標準錯誤輸出輸出 log,通過
log_to_stderr_
開關控制; - 最終輸出的日誌信息中是否要包含線程信息和時間戳,通過
thread_
和timestamp_
開關控制; - 要輸出的 log 後端
streams_
。
這裏先看一下這套 log 系統支持的 log severity:
// Note that the non-standard LoggingSeverity aliases exist because they are
// still in broad use. The meanings of the levels are:
// LS_VERBOSE: This level is for data which we do not want to appear in the
// normal debug log, but should appear in diagnostic logs.
// LS_INFO: Chatty level used in debugging for all sorts of things, the default
// in debug builds.
// LS_WARNING: Something that may warrant investigation.
// LS_ERROR: Something that should not have occurred.
// LS_NONE: Don't log.
enum LoggingSeverity {
LS_VERBOSE,
LS_INFO,
LS_WARNING,
LS_ERROR,
LS_NONE,
INFO = LS_INFO,
WARNING = LS_WARNING,
LERROR = LS_ERROR
};
這套 log 系統包括 LS_NONE
支持 5 級 severity,各級 severity 的使用說明,如上面代碼中的註釋。
LogMessage
用一個靜態的 StreamList
保存 log 後端 LogSink
:
private:
. . . . . .
typedef std::pair<LogSink*, LoggingSeverity> StreamAndSeverity;
typedef std::list<StreamAndSeverity> StreamList;
. . . . . .
static StreamList streams_;
LogMessage
用一個列表記錄 LogSink
對象的指針及其期望接收的 log 的最低的 severity。
LogMessage
提供了一些接口來訪問這些全局狀態。
LogToDebug()
/GetLogToDebug()
可以用於存取 g_dbg_sev
:
LoggingSeverity LogMessage::GetLogToDebug() {
return g_dbg_sev;
}
. . . . . .
void LogMessage::LogToDebug(LoggingSeverity min_sev) {
g_dbg_sev = min_sev;
CritScope cs(&g_log_crit);
UpdateMinLogSeverity();
}
SetLogToStderr()
可以用於設置 log_to_stderr_ 格式化開關:
void LogMessage::SetLogToStderr(bool log_to_stderr) {
log_to_stderr_ = log_to_stderr;
}
GetLogToStream()
/AddLogToStream()
/RemoveLogToStream()
可以用於訪問 streams_:
int LogMessage::GetLogToStream(LogSink* stream) {
CritScope cs(&g_log_crit);
LoggingSeverity sev = LS_NONE;
for (auto& kv : streams_) {
if (!stream || stream == kv.first) {
sev = std::min(sev, kv.second);
}
}
return sev;
}
void LogMessage::AddLogToStream(LogSink* stream, LoggingSeverity min_sev) {
CritScope cs(&g_log_crit);
streams_.push_back(std::make_pair(stream, min_sev));
UpdateMinLogSeverity();
}
void LogMessage::RemoveLogToStream(LogSink* stream) {
CritScope cs(&g_log_crit);
for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
if (stream == it->first) {
streams_.erase(it);
break;
}
}
UpdateMinLogSeverity();
}
g_min_sev
的狀態由各個 LogSink
接收 log 的最低 severity 和 g_dbg_sev
的狀態共同決定,具體來說,它是 g_dbg_sev
和各個 LogSink
接收 log 的最低 severity 中的最低 severity:
void LogMessage::UpdateMinLogSeverity()
RTC_EXCLUSIVE_LOCKS_REQUIRED(g_log_crit) {
LoggingSeverity min_sev = g_dbg_sev;
for (const auto& kv : streams_) {
const LoggingSeverity sev = kv.second;
min_sev = std::min(min_sev, sev);
}
g_min_sev = min_sev;
}
LogMessage
提供了幾個函數用於訪問 g_min_sev
:
bool LogMessage::Loggable(LoggingSeverity sev) {
return sev >= g_min_sev;
}
int LogMessage::GetMinLogSeverity() {
return g_min_sev;
}
LogThreads()
/LogTimestamps()
可以用於設置 thread_
和 timestamp_
格式化開關:
void LogMessage::LogThreads(bool on) {
thread_ = on;
}
void LogMessage::LogTimestamps(bool on) {
timestamp_ = on;
}
除了上面這些專用的控制接口之外,LogMessage
還提供了另一個函數 ConfigureLogging()
對 log 系統進行控制:
void LogMessage::ConfigureLogging(const char* params) {
LoggingSeverity current_level = LS_VERBOSE;
LoggingSeverity debug_level = GetLogToDebug();
std::vector<std::string> tokens;
tokenize(params, ' ', &tokens);
for (const std::string& token : tokens) {
if (token.empty())
continue;
// Logging features
if (token == "tstamp") {
LogTimestamps();
} else if (token == "thread") {
LogThreads();
// Logging levels
} else if (token == "verbose") {
current_level = LS_VERBOSE;
} else if (token == "info") {
current_level = LS_INFO;
} else if (token == "warning") {
current_level = LS_WARNING;
} else if (token == "error") {
current_level = LS_ERROR;
} else if (token == "none") {
current_level = LS_NONE;
// Logging targets
} else if (token == "debug") {
debug_level = current_level;
}
}
#if defined(WEBRTC_WIN) && !defined(WINUWP)
if ((LS_NONE != debug_level) && !::IsDebuggerPresent()) {
// First, attempt to attach to our parent's console... so if you invoke
// from the command line, we'll see the output there. Otherwise, create
// our own console window.
// Note: These methods fail if a console already exists, which is fine.
if (!AttachConsole(ATTACH_PARENT_PROCESS))
::AllocConsole();
}
#endif // defined(WEBRTC_WIN) && !defined(WINUWP)
LogToDebug(debug_level);
}
可以給這個函數傳入一個字符串,同時修改 g_dbg_sev
、thread_
和 timestamp_
等多個狀態。
g_dbg_sev
、log_to_stderr_
和 g_min_sev
之間的關係:g_min_sev
定義了 log 系統輸出的 log 的最低 severity,當要輸出的 log 的 severity 低於 g_min_sev
時,LogMessage
會認爲它是不需要輸出的。debug log 輸出可以看作是一個特殊的封裝了向標準錯誤輸出或本地系統特定的 log 輸出系統打印 log 的 LogSink
,g_dbg_sev
用於控制向這個 LogSink
中輸出的 log 的最低 severity,而 log_to_stderr_
則用於控制是否最終把 log 輸出到標準錯誤輸出。
對於上述操作全局狀態的函數做一下總結,如下表所示:
輸出 log
這個 log 系統輸出的完整 log 信息如下面這樣:
[002:138] [6134] (bitrate_prober.cc:64): Bandwidth probing enabled, set to inactive
[002:138] [6134] (rtp_transport_controller_send.cc:45): Using TaskQueue based SSCC
[002:138] [6134] (paced_sender.cc:362): ProcessThreadAttached 0x0x7ffbd42ece10
[002:138] [6134] (aimd_rate_control.cc:108): Using aimd rate control with back off factor 0.85
[002:138] [6134] (remote_bitrate_estimator_single_stream.cc:55): RemoteBitrateEstimatorSingleStream: Instantiating.
[002:138] [6134] (call.cc:1182): UpdateAggregateNetworkState: aggregate_state=down
[002:138] [6134] (send_side_congestion_controller.cc:581): SignalNetworkState Down
其中包含了時間戳和線程號。
LogMessage
對象持有一個 rtc::StringBuilder
類型的對象 print_stream_
,要最終輸出的內容,都會先被送進 print_stream_
。LogMessage
對象構造時,根據上面介紹的全局性的控制狀態,向 print_stream_
輸出 log header,主要包括 log 輸出的文件名和行號,可能包括時間戳和線程號。之後 LogMessage
的使用者可以獲得它的 rtc::StringBuilder
,並向其中輸出任何數量和格式的 log 信息。在 LogMessage
對象析構時,會從 print_stream_
獲得字符串,並把字符串輸出到 debug log,或者外部註冊的 LogSink
中。
LogMessage
對象的構造過程如下:
LogMessage::LogMessage(const char* file, int line, LoggingSeverity sev)
: LogMessage(file, line, sev, ERRCTX_NONE, 0) {}
LogMessage::LogMessage(const char* file,
int line,
LoggingSeverity sev,
LogErrorContext err_ctx,
int err)
: severity_(sev) {
if (timestamp_) {
// Use SystemTimeMillis so that even if tests use fake clocks, the timestamp
// in log messages represents the real system time.
int64_t time = TimeDiff(SystemTimeMillis(), LogStartTime());
// Also ensure WallClockStartTime is initialized, so that it matches
// LogStartTime.
WallClockStartTime();
print_stream_ << "[" << rtc::LeftPad('0', 3, rtc::ToString(time / 1000))
<< ":" << rtc::LeftPad('0', 3, rtc::ToString(time % 1000))
<< "] ";
}
if (thread_) {
PlatformThreadId id = CurrentThreadId();
print_stream_ << "[" << id << "] ";
}
if (file != nullptr) {
#if defined(WEBRTC_ANDROID)
tag_ = FilenameFromPath(file);
print_stream_ << "(line " << line << "): ";
#else
print_stream_ << "(" << FilenameFromPath(file) << ":" << line << "): ";
#endif
}
if (err_ctx != ERRCTX_NONE) {
char tmp_buf[1024];
SimpleStringBuilder tmp(tmp_buf);
tmp.AppendFormat("[0x%08X]", err);
switch (err_ctx) {
case ERRCTX_ERRNO:
tmp << " " << strerror(err);
break;
#ifdef WEBRTC_WIN
case ERRCTX_HRESULT: {
char msgbuf[256];
DWORD flags =
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
if (DWORD len = FormatMessageA(
flags, nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
msgbuf, sizeof(msgbuf) / sizeof(msgbuf[0]), nullptr)) {
while ((len > 0) &&
isspace(static_cast<unsigned char>(msgbuf[len - 1]))) {
msgbuf[--len] = 0;
}
tmp << " " << msgbuf;
}
break;
}
#endif // WEBRTC_WIN
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
case ERRCTX_OSSTATUS: {
std::string desc(DescriptionFromOSStatus(err));
tmp << " " << (desc.empty() ? "Unknown error" : desc.c_str());
break;
}
#endif // WEBRTC_MAC && !defined(WEBRTC_IOS)
default:
break;
}
extra_ = tmp.str();
}
}
#if defined(WEBRTC_ANDROID)
LogMessage::LogMessage(const char* file,
int line,
LoggingSeverity sev,
const char* tag)
: LogMessage(file, line, sev, ERRCTX_NONE, 0 /* err */) {
tag_ = tag;
print_stream_ << tag << ": ";
}
#endif
// DEPRECATED. Currently only used by downstream projects that use
// implementation details of logging.h. Work is ongoing to remove those
// dependencies.
LogMessage::LogMessage(const char* file,
int line,
LoggingSeverity sev,
const std::string& tag)
: LogMessage(file, line, sev) {
print_stream_ << tag << ": ";
}
LogStartTime()
用於記錄或獲得首次日誌時間,WallClockStartTime()
則似乎沒有看到其應用場合:
int64_t LogMessage::LogStartTime() {
static const int64_t g_start = SystemTimeMillis();
return g_start;
}
uint32_t LogMessage::WallClockStartTime() {
static const uint32_t g_start_wallclock = time(nullptr);
return g_start_wallclock;
}
WebRTC 的 log 消息中的時間戳是相對時間,而不是絕對的牆上時間。
FilenameFromPath()
函數用於從文件的絕對路徑中提取文件名:
// Return the filename portion of the string (that following the last slash).
const char* FilenameFromPath(const char* file) {
const char* end1 = ::strrchr(file, '/');
const char* end2 = ::strrchr(file, '\\');
if (!end1 && !end2)
return file;
else
return (end1 > end2) ? end1 + 1 : end2 + 1;
}
LogMessage
的使用者可以通過其 stream()
函數獲得其 rtc::StringBuilder
:
rtc::StringBuilder& LogMessage::stream() {
return print_stream_;
}
LogMessage
對象的析構過程如下:
LogMessage::~LogMessage() {
FinishPrintStream();
const std::string str = print_stream_.Release();
if (severity_ >= g_dbg_sev) {
#if defined(WEBRTC_ANDROID)
OutputToDebug(str, severity_, tag_);
#else
OutputToDebug(str, severity_);
#endif
}
CritScope cs(&g_log_crit);
for (auto& kv : streams_) {
if (severity_ >= kv.second) {
#if defined(WEBRTC_ANDROID)
kv.first->OnLogMessage(str, severity_, tag_);
#else
kv.first->OnLogMessage(str, severity_);
#endif
}
}
}
在要輸出的 log 的 severity 高於 g_dbg_sev
時,LogMessage
對象的析構函數會將 log 輸出到 debug;LogMessage
對象的析構函數還會把 log 輸出到所有其請求的最低 severity 低於當前這條 log 消息的 severity 的 LogSink
。
所謂的輸出到 debug 的過程 OutputToDebug()
如下:
#if defined(WEBRTC_ANDROID)
void LogMessage::OutputToDebug(const std::string& str,
LoggingSeverity severity,
const char* tag) {
#else
void LogMessage::OutputToDebug(const std::string& str,
LoggingSeverity severity) {
#endif
bool log_to_stderr = log_to_stderr_;
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) && defined(NDEBUG)
// On the Mac, all stderr output goes to the Console log and causes clutter.
// So in opt builds, don't log to stderr unless the user specifically sets
// a preference to do so.
CFStringRef key = CFStringCreateWithCString(
kCFAllocatorDefault, "logToStdErr", kCFStringEncodingUTF8);
CFStringRef domain = CFBundleGetIdentifier(CFBundleGetMainBundle());
if (key != nullptr && domain != nullptr) {
Boolean exists_and_is_valid;
Boolean should_log =
CFPreferencesGetAppBooleanValue(key, domain, &exists_and_is_valid);
// If the key doesn't exist or is invalid or is false, we will not log to
// stderr.
log_to_stderr = exists_and_is_valid && should_log;
}
if (key != nullptr) {
CFRelease(key);
}
#endif // defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) && defined(NDEBUG)
#if defined(WEBRTC_WIN)
// Always log to the debugger.
// Perhaps stderr should be controlled by a preference, as on Mac?
OutputDebugStringA(str.c_str());
if (log_to_stderr) {
// This handles dynamically allocated consoles, too.
if (HANDLE error_handle = ::GetStdHandle(STD_ERROR_HANDLE)) {
log_to_stderr = false;
DWORD written = 0;
::WriteFile(error_handle, str.data(), static_cast<DWORD>(str.size()),
&written, 0);
}
}
#endif // WEBRTC_WIN
#if defined(WEBRTC_ANDROID)
// Android's logging facility uses severity to log messages but we
// need to map libjingle's severity levels to Android ones first.
// Also write to stderr which maybe available to executable started
// from the shell.
int prio;
switch (severity) {
case LS_VERBOSE:
prio = ANDROID_LOG_VERBOSE;
break;
case LS_INFO:
prio = ANDROID_LOG_INFO;
break;
case LS_WARNING:
prio = ANDROID_LOG_WARN;
break;
case LS_ERROR:
prio = ANDROID_LOG_ERROR;
break;
default:
prio = ANDROID_LOG_UNKNOWN;
}
int size = str.size();
int line = 0;
int idx = 0;
const int max_lines = size / kMaxLogLineSize + 1;
if (max_lines == 1) {
__android_log_print(prio, tag, "%.*s", size, str.c_str());
} else {
while (size > 0) {
const int len = std::min(size, kMaxLogLineSize);
// Use the size of the string in the format (str may have \0 in the
// middle).
__android_log_print(prio, tag, "[%d/%d] %.*s", line + 1, max_lines, len,
str.c_str() + idx);
idx += len;
size -= len;
++line;
}
}
#endif // WEBRTC_ANDROID
if (log_to_stderr) {
fprintf(stderr, "%s", str.c_str());
fflush(stderr);
}
}
OutputToDebug()
將日誌輸出到本地系統特定的 log 系統或標準錯誤輸出,如 Android 的 logcat。前面介紹的 log_to_stderr_
開關在這個函數中起作用。
Log 輸出用戶接口及其實現
這套 log 系統的用戶一般不會直接使用 LogMessage
打 log,而是會使用基於 LogMessage
封裝的宏,如 RTC_LOG()
和 RTC_DLOG()
等,像下面這樣:
RTC_LOG(INFO) << "AudioDeviceBuffer::~dtor";
RTC_LOG(LS_ERROR) << "Failed to set audio transport since media was active";
RTC_LOG(INFO) << "Terminate";
RTC_DLOG(LS_WARNING) << "Stereo mode is enabled";
log 系統的用戶使用的宏主要有 RTC_DLOG*
系列和 RTC_LOG*
系列。RTC_DLOG
宏與他們對應的 RTC_LOG
宏是等價的,除了它們只在 debug builds 中才生成代碼外,RTC_LOG
系列宏的定義如下:
// The RTC_DLOG macros are equivalent to their RTC_LOG counterparts except that
// they only generate code in debug builds.
#if RTC_DLOG_IS_ON
#define RTC_DLOG(sev) RTC_LOG(sev)
#define RTC_DLOG_V(sev) RTC_LOG_V(sev)
#define RTC_DLOG_F(sev) RTC_LOG_F(sev)
#else
#define RTC_DLOG_EAT_STREAM_PARAMS() \
while (false) \
rtc::webrtc_logging_impl::LogStreamer<>()
#define RTC_DLOG(sev) RTC_DLOG_EAT_STREAM_PARAMS()
#define RTC_DLOG_V(sev) RTC_DLOG_EAT_STREAM_PARAMS()
#define RTC_DLOG_F(sev) RTC_DLOG_EAT_STREAM_PARAMS()
#endif
Android 平臺有一個平臺專用的可以帶上 TAG 的宏:
#ifdef WEBRTC_ANDROID
namespace webrtc_logging_impl {
// TODO(kwiberg): Replace these with absl::string_view.
inline const char* AdaptString(const char* str) {
return str;
}
inline const char* AdaptString(const std::string& str) {
return str.c_str();
}
} // namespace webrtc_logging_impl
#define RTC_LOG_TAG(sev, tag) \
rtc::webrtc_logging_impl::LogCall() & \
rtc::webrtc_logging_impl::LogStreamer<>() \
<< rtc::webrtc_logging_impl::LogMetadataTag { \
sev, rtc::webrtc_logging_impl::AdaptString(tag) \
}
#else
// DEPRECATED. This macro is only intended for Android.
#define RTC_LOG_TAG(sev, tag) RTC_LOG_V(sev)
#endif
Windows 平臺也有一些平臺專用的宏:
#if defined(WEBRTC_WIN)
#define RTC_LOG_GLE_EX(sev, err) RTC_LOG_E(sev, HRESULT, err)
#define RTC_LOG_GLE(sev) RTC_LOG_GLE_EX(sev, static_cast<int>(GetLastError()))
#define RTC_LOG_ERR_EX(sev, err) RTC_LOG_GLE_EX(sev, err)
#define RTC_LOG_ERR(sev) RTC_LOG_GLE(sev)
#elif defined(__native_client__) && __native_client__
#define RTC_LOG_ERR_EX(sev, err) RTC_LOG(sev)
#define RTC_LOG_ERR(sev) RTC_LOG(sev)
#elif defined(WEBRTC_POSIX)
#define RTC_LOG_ERR_EX(sev, err) RTC_LOG_ERRNO_EX(sev, err)
#define RTC_LOG_ERR(sev) RTC_LOG_ERRNO(sev)
#endif // WEBRTC_WIN
RTC_LOG
系列宏的實現如下:
//////////////////////////////////////////////////////////////////////
// Logging Helpers
//////////////////////////////////////////////////////////////////////
#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_LOG_FILE_LINE(rtc::sev, __FILE__, __LINE__)
// The _V version is for when a variable is passed in.
#define RTC_LOG_V(sev) RTC_LOG_FILE_LINE(sev, __FILE__, __LINE__)
// The _F version prefixes the message with the current function name.
#if (defined(__GNUC__) && !defined(NDEBUG)) || defined(WANT_PRETTY_LOG_F)
#define RTC_LOG_F(sev) RTC_LOG(sev) << __PRETTY_FUNCTION__ << ": "
#define RTC_LOG_T_F(sev) \
RTC_LOG(sev) << this << ": " << __PRETTY_FUNCTION__ << ": "
#else
#define RTC_LOG_F(sev) RTC_LOG(sev) << __FUNCTION__ << ": "
#define RTC_LOG_T_F(sev) RTC_LOG(sev) << this << ": " << __FUNCTION__ << ": "
#endif
#define RTC_LOG_CHECK_LEVEL(sev) rtc::LogCheckLevel(rtc::sev)
#define RTC_LOG_CHECK_LEVEL_V(sev) rtc::LogCheckLevel(sev)
inline bool LogCheckLevel(LoggingSeverity sev) {
return (LogMessage::GetMinLogSeverity() <= sev);
}
#define RTC_LOG_E(sev, ctx, err) \
rtc::webrtc_logging_impl::LogCall() & \
rtc::webrtc_logging_impl::LogStreamer<>() \
<< rtc::webrtc_logging_impl::LogMetadataErr { \
{__FILE__, __LINE__, rtc::sev}, rtc::ERRCTX_##ctx, (err) \
}
#define RTC_LOG_T(sev) RTC_LOG(sev) << this << ": "
#define RTC_LOG_ERRNO_EX(sev, err) RTC_LOG_E(sev, ERRNO, err)
#define RTC_LOG_ERRNO(sev) RTC_LOG_ERRNO_EX(sev, errno)
RTC_LOG_T
宏會把當前類的 this
指針的值在 log 消息中顯示出來。RTC_LOG_T
宏基於 RTC_LOG
宏實現。
RTC_LOG_F
宏會把函數的函數名在 log 消息中顯示出來。
RTC_LOG_T_F
宏則會把當前類的 this
指針和函數的函數名在 log 消息中都顯示出來。
在平臺支持 __PRETTY_FUNCTION__
時,RTC_LOG_F
和 RTC_LOG_T_F
宏還會以更漂亮的格式顯示函數名,即會連同函數的簽名一起來顯示函數名。RTC_LOG_F
和 RTC_LOG_T_F
宏都基於 RTC_LOG
宏實現。
RTC_LOG_V
宏與 RTC_LOG
宏基本相同,除了它的 severity 參數可以是一個變量外。RTC_LOG_V
宏與 RTC_LOG
宏都基於 RTC_LOG_FILE_LINE
宏實現。感覺這裏的三個宏可以省掉一個,讓 RTC_LOG
宏基於 RTC_LOG_V
宏實現,如下面這樣:
#define RTC_LOG_V(sev) \
rtc::webrtc_logging_impl::LogCall() & \
rtc::webrtc_logging_impl::LogStreamer<>() \
<< rtc::webrtc_logging_impl::LogMetadata(__FILE__, __LINE__, sev)
#define RTC_LOG(sev) RTC_LOG_V(rtc::sev)
RTC_LOG_FILE_LINE
宏基於 LogCall
、LogStreamer
和 LogMetadata
等類實現。
RTC_LOG_ERRNO
和 RTC_LOG_ERRNO_EX
宏還會在 log 消息中帶上一些錯誤相關的信息。這兩個宏基於 RTC_LOG_E
宏實現。與 RTC_LOG_FILE_LINE
宏類似,RTC_LOG_E
也是基於 LogCall
和 LogStreamer
等類實現。
來看 RTC_LOG_FILE_LINE
宏的實現:
#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)
首先看一下 LogCall
類的定義:
class LogCall final {
public:
// This can be any binary operator with precedence lower than <<.
template <typename... Ts>
RTC_FORCE_INLINE void operator&(const LogStreamer<Ts...>& streamer) {
streamer.Call();
}
};
LogCall
類只定義了一個成員函數,即 operator&()
,這個成員函數接收一個 LogStreamer
參數。如 operator&()
成員函數的註釋的說明,這裏重載的操作符不要求一定是 &
,只要是一個比 <<
操作符優先級低的操作符即可。
站在 LogCall
類的角度看,RTC_LOG_FILE_LINE
宏完成的工作是:1. 創建一個 LogCall
類的對象;2. 以動態的方式創建一個 LogStreamer
對象;3. 傳入創建的 LogStreamer
對象,在創建的 LogCall
類對象上調用其成員函數。
RTC_LOG_FILE_LINE
宏的意圖是,以動態的方式創建一個 LogStreamer
對象,並以該對象爲參數,調用某個函數,被調用的這個函數,把 LogStreamer
對象的 log 消息輸出出去。
把 RTC_LOG_FILE_LINE
宏換一種實現方式,來更清晰地看一下這個宏的實現。首先給 LogCall
類增加一個函數:
template <typename... Ts>
RTC_FORCE_INLINE void func(const LogStreamer<Ts...>& streamer) {
streamer.Call();
}
然後修改 RTC_LOG_FILE_LINE
宏的實現方式:
#define RTC_LOG_FILE_LINE_HAN(sev, file, line) \
rtc::webrtc_logging_impl::LogCall().func( \
rtc::webrtc_logging_impl::LogStreamer<>() \
<< rtc::webrtc_logging_impl::LogMetadata(file, line, sev)
#define RTC_LOG_HAN(sev) RTC_LOG_FILE_LINE_HAN(rtc::sev, __FILE__, __LINE__)
// The _V version is for when a variable is passed in.
#define RTC_LOG_V_HAN(sev) RTC_LOG_FILE_LINE_HAN(sev, __FILE__, __LINE__)
注意,對 LogCall
類的 operator&()
的調用被替換爲了對成員函數 func()
的調用。
經過了這樣的修改,使用 RTC_LOG
的代碼也要做一些修改,如下:
RTC_LOG(INFO) << __FUNCTION__;
RTC_LOG_HAN(INFO) << __FUNCTION__ << " HAN" );
注意,相對於原來的 RTC_LOG
,修改之後的 RTC_LOG
宏在使用時,需要在語句的最後添加一個右括號。使用時最後的右括號顯得非常奇怪。從中我們也可以體會一下 WebRTC 用 operator&()
實現 LogCall
類的原因。
LogStreamer
模板類的定義如下:
// Ephemeral type that represents the result of the logging << operator.
template <typename... Ts>
class LogStreamer;
// Base case: Before the first << argument.
template <>
class LogStreamer<> final {
public:
template <typename U,
typename std::enable_if<std::is_arithmetic<U>::value ||
std::is_enum<U>::value>::type* = nullptr>
RTC_FORCE_INLINE LogStreamer<decltype(MakeVal(std::declval<U>()))> operator<<(
U arg) const {
return LogStreamer<decltype(MakeVal(std::declval<U>()))>(MakeVal(arg),
this);
}
template <typename U,
typename std::enable_if<!std::is_arithmetic<U>::value &&
!std::is_enum<U>::value>::type* = nullptr>
RTC_FORCE_INLINE LogStreamer<decltype(MakeVal(std::declval<U>()))> operator<<(
const U& arg) const {
return LogStreamer<decltype(MakeVal(std::declval<U>()))>(MakeVal(arg),
this);
}
template <typename... Us>
RTC_FORCE_INLINE static void Call(const Us&... args) {
static constexpr LogArgType t[] = {Us::Type()..., LogArgType::kEnd};
Log(t, args.GetVal()...);
}
};
// Inductive case: We've already seen at least one << argument. The most recent
// one had type `T`, and the earlier ones had types `Ts`.
template <typename T, typename... Ts>
class LogStreamer<T, Ts...> final {
public:
RTC_FORCE_INLINE LogStreamer(T arg, const LogStreamer<Ts...>* prior)
: arg_(arg), prior_(prior) {}
template <typename U,
typename std::enable_if<std::is_arithmetic<U>::value ||
std::is_enum<U>::value>::type* = nullptr>
RTC_FORCE_INLINE LogStreamer<decltype(MakeVal(std::declval<U>())), T, Ts...>
operator<<(U arg) const {
return LogStreamer<decltype(MakeVal(std::declval<U>())), T, Ts...>(
MakeVal(arg), this);
}
template <typename U,
typename std::enable_if<!std::is_arithmetic<U>::value &&
!std::is_enum<U>::value>::type* = nullptr>
RTC_FORCE_INLINE LogStreamer<decltype(MakeVal(std::declval<U>())), T, Ts...>
operator<<(const U& arg) const {
return LogStreamer<decltype(MakeVal(std::declval<U>())), T, Ts...>(
MakeVal(arg), this);
}
template <typename... Us>
RTC_FORCE_INLINE void Call(const Us&... args) const {
prior_->Call(arg_, args...);
}
private:
// The most recent argument.
T arg_;
// Earlier arguments.
const LogStreamer<Ts...>* prior_;
};
對 LogStreamer
模板類的 operator<<()
成員的連續調用,創建一個 LogStreamer
模板類對象的單鏈表,創建的每個新的鏈表節點會被插入鏈表的頭部。也就是說,使用 RTC_LOG
宏時,先傳入的參數所對應的 LogStreamer
對象會位於鏈表的後面。鏈表的節點的值的類型爲 Val
:
template <LogArgType N, typename T>
struct Val {
static constexpr LogArgType Type() { return N; }
T GetVal() const { return val; }
T val;
};
在 LogCall
類的 operator&()
函數中調用 LogStreamer
對象的 Call()
函數,將調用最後創建的 LogStreamer
對象的 Call()
,將觸發一系列的對 LogStreamer
對象的 Call()
函數的調用,最終將調用 LogStreamer<>
的 Call()
函數。由模板類 LogStreamer
的 Call()
函數的實現,可以知道,LogStreamer<>
的 Call()
函數將以使用 RTC_LOG
宏時參數傳入的先後順序接收各個參數。
LogStreamer<>
的 Call()
函數提取各個參數的類型,構造類型數組,並將類型數組和各個參數的實際值傳給 Log()
來輸出 log。
Log()
函數的定義如下:
namespace webrtc_logging_impl {
void Log(const LogArgType* fmt, ...) {
va_list args;
va_start(args, fmt);
LogMetadataErr meta;
const char* tag = nullptr;
switch (*fmt) {
case LogArgType::kLogMetadata: {
meta = {va_arg(args, LogMetadata), ERRCTX_NONE, 0};
break;
}
case LogArgType::kLogMetadataErr: {
meta = va_arg(args, LogMetadataErr);
break;
}
#ifdef WEBRTC_ANDROID
case LogArgType::kLogMetadataTag: {
const LogMetadataTag tag_meta = va_arg(args, LogMetadataTag);
meta = {{nullptr, 0, tag_meta.severity}, ERRCTX_NONE, 0};
tag = tag_meta.tag;
break;
}
#endif
default: {
RTC_NOTREACHED();
va_end(args);
return;
}
}
LogMessage log_message(meta.meta.File(), meta.meta.Line(),
meta.meta.Severity(), meta.err_ctx, meta.err);
if (tag) {
log_message.AddTag(tag);
}
for (++fmt; *fmt != LogArgType::kEnd; ++fmt) {
switch (*fmt) {
case LogArgType::kInt:
log_message.stream() << va_arg(args, int);
break;
case LogArgType::kLong:
log_message.stream() << va_arg(args, long);
break;
case LogArgType::kLongLong:
log_message.stream() << va_arg(args, long long);
break;
case LogArgType::kUInt:
log_message.stream() << va_arg(args, unsigned);
break;
case LogArgType::kULong:
log_message.stream() << va_arg(args, unsigned long);
break;
case LogArgType::kULongLong:
log_message.stream() << va_arg(args, unsigned long long);
break;
case LogArgType::kDouble:
log_message.stream() << va_arg(args, double);
break;
case LogArgType::kLongDouble:
log_message.stream() << va_arg(args, long double);
break;
case LogArgType::kCharP:
log_message.stream() << va_arg(args, const char*);
break;
case LogArgType::kStdString:
log_message.stream() << *va_arg(args, const std::string*);
break;
case LogArgType::kVoidP:
log_message.stream() << va_arg(args, const void*);
break;
default:
RTC_NOTREACHED();
va_end(args);
return;
}
}
va_end(args);
}
} // namespace webrtc_logging_impl
Log()
函數構造 LogMessage
對象,並將各個要輸出的 log 值送進 LogMessage
的 rtc::StringBuilder
。
WebRTC 實現的 LogSink
默認情況下,LogMessage
的 streams_
中沒有註冊任何 LogSink
,然而 WebRTC 實際上還是提供了幾個 LogSink
的實現的。具體而言,是在 webrtc/src/rtc_base/logsinks.h
中,有兩個 LogSink
的實現,它們分別是類 FileRotatingLogSink
和 CallSessionFileRotatingLogSink
。
FileRotatingLogSink
使用一個 FileRotatingStream
把日誌寫入一個支持 rotate 的文件。這個類的聲明如下:
// Log sink that uses a FileRotatingStream to write to disk.
// Init() must be called before adding this sink.
class FileRotatingLogSink : public LogSink {
public:
// |num_log_files| must be greater than 1 and |max_log_size| must be greater
// than 0.
FileRotatingLogSink(const std::string& log_dir_path,
const std::string& log_prefix,
size_t max_log_size,
size_t num_log_files);
~FileRotatingLogSink() override;
// Writes the message to the current file. It will spill over to the next
// file if needed.
void OnLogMessage(const std::string& message) override;
void OnLogMessage(const std::string& message,
LoggingSeverity sev,
const char* tag) override;
// Deletes any existing files in the directory and creates a new log file.
virtual bool Init();
// Disables buffering on the underlying stream.
bool DisableBuffering();
protected:
explicit FileRotatingLogSink(FileRotatingStream* stream);
private:
std::unique_ptr<FileRotatingStream> stream_;
RTC_DISALLOW_COPY_AND_ASSIGN(FileRotatingLogSink);
};
這個類是對 FileRotatingStream
的一個不是很複雜的封裝,它的實現如下:
FileRotatingLogSink::FileRotatingLogSink(const std::string& log_dir_path,
const std::string& log_prefix,
size_t max_log_size,
size_t num_log_files)
: FileRotatingLogSink(new FileRotatingStream(log_dir_path,
log_prefix,
max_log_size,
num_log_files)) {}
FileRotatingLogSink::FileRotatingLogSink(FileRotatingStream* stream)
: stream_(stream) {
RTC_DCHECK(stream);
}
FileRotatingLogSink::~FileRotatingLogSink() {}
void FileRotatingLogSink::OnLogMessage(const std::string& message) {
if (stream_->GetState() != SS_OPEN) {
std::fprintf(stderr, "Init() must be called before adding this sink.\n");
return;
}
stream_->WriteAll(message.c_str(), message.size(), nullptr, nullptr);
}
void FileRotatingLogSink::OnLogMessage(const std::string& message,
LoggingSeverity sev,
const char* tag) {
if (stream_->GetState() != SS_OPEN) {
std::fprintf(stderr, "Init() must be called before adding this sink.\n");
return;
}
stream_->WriteAll(tag, strlen(tag), nullptr, nullptr);
stream_->WriteAll(": ", 2, nullptr, nullptr);
stream_->WriteAll(message.c_str(), message.size(), nullptr, nullptr);
}
bool FileRotatingLogSink::Init() {
return stream_->Open();
}
bool FileRotatingLogSink::DisableBuffering() {
return stream_->DisableBuffering();
}
FileRotatingLogSink
在收到日誌消息時,簡單地把日誌消息寫入 FileRotatingStream
。
CallSessionFileRotatingLogSink
與 FileRotatingLogSink
類似,僅有的區別是,它使用的是 CallSessionFileRotatingStream
。這個類的聲明如下:
// Log sink that uses a CallSessionFileRotatingStream to write to disk.
// Init() must be called before adding this sink.
class CallSessionFileRotatingLogSink : public FileRotatingLogSink {
public:
CallSessionFileRotatingLogSink(const std::string& log_dir_path,
size_t max_total_log_size);
~CallSessionFileRotatingLogSink() override;
private:
RTC_DISALLOW_COPY_AND_ASSIGN(CallSessionFileRotatingLogSink);
};
CallSessionFileRotatingLogSink
的實現如下:
CallSessionFileRotatingLogSink::CallSessionFileRotatingLogSink(
const std::string& log_dir_path,
size_t max_total_log_size)
: FileRotatingLogSink(
new CallSessionFileRotatingStream(log_dir_path, max_total_log_size)) {
}
CallSessionFileRotatingLogSink::~CallSessionFileRotatingLogSink() {}
. . .
WebRTC 的 rtc_base 的 log 系統實現,還有幾點看得比較暈的地方:
LogStreamer
模板類的實現。LogStreamer
模板類的實現用了一些模板元編程的技巧,std::enable_if
、std::decay
等 STL 的組件的使用讓人覺得有點不太容易懂。- 函數可變參數列表的實現原理。可變參數列表的使用不獨於此,其原理是需要明白的比較重要的一個 C/C++ 開發的技術點。
關於 WebRTC 的 rtc_base log 系統實現:
- log 系統除了要功能強大,性能優異,還需要提供一個非常好用的用戶接口。易用的用戶接口的重要性,從這個 log 系統複雜的
LogStreamer
模板類實現就可見一斑。 - WebRTC 的 rtc_base log 系統實現在向
LogSink
輸出 log 時,使用了鎖做同步,log 輸出非常頻繁時,鎖爭搶可能會成爲一個問題。 - 輸出 log 一般都需要執行 IO 操作,不過 WebRTC 的 rtc_base log 系統實現沒有把 log 輸出異步化,輸出 log 都是同步操作,即使專門實現的
LogSink
FileRotatingLogSink
和CallSessionFileRotatingLogSink
也是。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://www.jianshu.com/p/adf28bfb3d94?ivk_sa=1024320u