日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

WebRTC 的 log 系统实现分析

發(fā)布時(shí)間:2024/4/11 windows 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WebRTC 的 log 系统实现分析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

WebRTC 有多套 log 輸出系統(tǒng),一套位于 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 輸出系統(tǒng)。它可以非常方便地輸出類似于下面這種格式的 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 內(nèi)容信息。

整體來看,這個(gè) log 系統(tǒng)可以分為四層:對(duì)于 log 系統(tǒng)的用戶,看到的是用于輸出不同重要性的 log 的一組宏,如 RTC_LOG(sev),這組宏提供了類似于輸出流 std::ostream 的接口來輸出 log;log 系統(tǒng)的核心是 LogMessage,它用于對(duì)要輸出的 log 做格式化,產(chǎn)生最終要輸出的字符串,并把產(chǎn)生的字符串送給 log 后端完成 log 輸出,同時(shí)還可以通過它對(duì) log 系統(tǒng)的行為施加控制;最下面的是 log 后端,log 后端用 LogSink 接口描述;在 RTC_LOG(sev) 等宏和 LogMessage 之間的是用于實(shí)現(xiàn) log 系統(tǒng)的用戶看到的類似于輸出流的接口的模板類 LogStreamer 和 LogCall 等。這個(gè) log 系統(tǒng)整體如下圖所示:

Log 后端

Log 后端用 LogSink 接口描述,這個(gè)接口聲明(位于 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 的兩個(gè)非純虛函數(shù)實(shí)現(xiàn)如下(位于 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 系統(tǒng)的全局控制

LogMessage 可以用于對(duì) log 系統(tǒng)施加一些全局性的控制,這主要是指如下這些全局性的狀態(tài):

  • log 輸出的最低 severity,通過 g_min_sev 控制;
  • debug 輸出的最低 severity,通過 g_dbg_sev 控制;
  • 是否要向標(biāo)準(zhǔn)錯(cuò)誤輸出輸出 log,通過 log_to_stderr_ 開關(guān)控制;
  • 最終輸出的日志信息中是否要包含線程信息和時(shí)間戳,通過 thread_ 和 timestamp_ 開關(guān)控制;
  • 要輸出的 log 后端 streams_。

這里先看一下這套 log 系統(tǒng)支持的 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 系統(tǒng)包括 LS_NONE 支持 5 級(jí) severity,各級(jí) severity 的使用說明,如上面代碼中的注釋。

LogMessage 用一個(gè)靜態(tài)的 StreamList 保存 log 后端 LogSink:

private: . . . . . .typedef std::pair<LogSink*, LoggingSeverity> StreamAndSeverity;typedef std::list<StreamAndSeverity> StreamList; . . . . . .static StreamList streams_;

LogMessage 用一個(gè)列表記錄 LogSink 對(duì)象的指針及其期望接收的 log 的最低的 severity。

LogMessage 提供了一些接口來訪問這些全局狀態(tài)。

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() 可以用于設(shè)置 log_to_stderr_ 格式化開關(guān):

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 的狀態(tài)由各個(gè) LogSink 接收 log 的最低 severity 和 g_dbg_sev 的狀態(tài)共同決定,具體來說,它是 g_dbg_sev 和各個(gè) 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è)函數(shù)用于訪問 g_min_sev:

bool LogMessage::Loggable(LoggingSeverity sev) {return sev >= g_min_sev; }int LogMessage::GetMinLogSeverity() {return g_min_sev; }

LogThreads()/LogTimestamps() 可以用于設(shè)置 thread_ 和 timestamp_ 格式化開關(guān):

void LogMessage::LogThreads(bool on) {thread_ = on; }void LogMessage::LogTimestamps(bool on) {timestamp_ = on; }

除了上面這些專用的控制接口之外,LogMessage 還提供了另一個(gè)函數(shù) ConfigureLogging() 對(duì) log 系統(tǒng)進(jìn)行控制:

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 featuresif (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è)函數(shù)傳入一個(gè)字符串,同時(shí)修改 g_dbg_sev、thread_ 和 timestamp_ 等多個(gè)狀態(tài)。

g_dbg_sev、log_to_stderr_ 和 g_min_sev 之間的關(guān)系:g_min_sev 定義了 log 系統(tǒng)輸出的 log 的最低 severity,當(dāng)要輸出的 log 的 severity 低于 g_min_sev 時(shí),LogMessage 會(huì)認(rèn)為它是不需要輸出的。debug log 輸出可以看作是一個(gè)特殊的封裝了向標(biāo)準(zhǔn)錯(cuò)誤輸出或本地系統(tǒng)特定的 log 輸出系統(tǒng)打印 log 的 LogSink,g_dbg_sev 用于控制向這個(gè) LogSink 中輸出的 log 的最低 severity,而 log_to_stderr_ 則用于控制是否最終把 log 輸出到標(biāo)準(zhǔn)錯(cuò)誤輸出。

對(duì)于上述操作全局狀態(tài)的函數(shù)做一下總結(jié),如下表所示:

全局狀態(tài)訪問接口說明
g_dbg_sevLogToDebug()/GetLogToDebug()debug 輸出的最低 severity
log_to_stderr_SetLogToStderr()是否要向標(biāo)準(zhǔn)錯(cuò)誤輸出輸出 log
streams_GetLogToStream()/AddLogToStream()/RemoveLogToStream()要輸出的 log 后端
g_min_sevGetMinLogSeverity()/IsNoop()/Loggable()狀態(tài)由各個(gè) LogSink 接收 log 的最低 severity 和 g_dbg_sev 的狀態(tài)共同決定
thread_LogThreads()最終輸出的日志信息中是否要包含線程信息
timestamp_LogTimestamps()最終輸出的日志信息中是否要包含時(shí)間戳

輸出 log

這個(gè) log 系統(tǒng)輸出的完整 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

其中包含了時(shí)間戳和線程號(hào)。

LogMessage 對(duì)象持有一個(gè) rtc::StringBuilder 類型的對(duì)象 print_stream_,要最終輸出的內(nèi)容,都會(huì)先被送進(jìn) print_stream_。LogMessage 對(duì)象構(gòu)造時(shí),根據(jù)上面介紹的全局性的控制狀態(tài),向 print_stream_ 輸出 log header,主要包括 log 輸出的文件名和行號(hào),可能包括時(shí)間戳和線程號(hào)。之后 LogMessage 的使用者可以獲得它的 rtc::StringBuilder,并向其中輸出任何數(shù)量和格式的 log 信息。在 LogMessage 對(duì)象析構(gòu)時(shí),會(huì)從 print_stream_ 獲得字符串,并把字符串輸出到 debug log,或者外部注冊(cè)的 LogSink 中。

LogMessage 對(duì)象的構(gòu)造過程如下:

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 << "): "; #elseprint_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_WINcase 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() 用于記錄或獲得首次日志時(shí)間,WallClockStartTime() 則似乎沒有看到其應(yīng)用場(chǎng)合:

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 消息中的時(shí)間戳是相對(duì)時(shí)間,而不是絕對(duì)的墻上時(shí)間。

FilenameFromPath() 函數(shù)用于從文件的絕對(duì)路徑中提取文件名:

// 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;elsereturn (end1 > end2) ? end1 + 1 : end2 + 1; }

LogMessage 的使用者可以通過其 stream() 函數(shù)獲得其 rtc::StringBuilder:

rtc::StringBuilder& LogMessage::stream() {return print_stream_; }

LogMessage 對(duì)象的析構(gòu)過程如下:

LogMessage::~LogMessage() {FinishPrintStream();const std::string str = print_stream_.Release();if (severity_ >= g_dbg_sev) { #if defined(WEBRTC_ANDROID)OutputToDebug(str, severity_, tag_); #elseOutputToDebug(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_); #elsekv.first->OnLogMessage(str, severity_); #endif}} }

在要輸出的 log 的 severity 高于 g_dbg_sev 時(shí),LogMessage 對(duì)象的析構(gòu)函數(shù)會(huì)將 log 輸出到 debug;LogMessage 對(duì)象的析構(gòu)函數(shù)還會(huì)把 log 輸出到所有其請(qǐng)求的最低 severity 低于當(dāng)前這條 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) { #endifbool 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_ANDROIDif (log_to_stderr) {fprintf(stderr, "%s", str.c_str());fflush(stderr);} }

OutputToDebug() 將日志輸出到本地系統(tǒng)特定的 log 系統(tǒng)或標(biāo)準(zhǔn)錯(cuò)誤輸出,如 Android 的 logcat。前面介紹的 log_to_stderr_ 開關(guān)在這個(gè)函數(shù)中起作用。

Log 輸出用戶接口及其實(shí)現(xiàn)

這套 log 系統(tǒng)的用戶一般不會(huì)直接使用 LogMessage 打 log,而是會(huì)使用基于 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 系統(tǒng)的用戶使用的宏主要有 RTC_DLOG* 系列和 RTC_LOG* 系列。RTC_DLOG 宏與他們對(duì)應(yīng)的 RTC_LOG 宏是等價(jià)的,除了它們只在 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 平臺(tái)有一個(gè)平臺(tái)專用的可以帶上 TAG 的宏:

#ifdef WEBRTC_ANDROIDnamespace 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 平臺(tái)也有一些平臺(tái)專用的宏:

#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 系列宏的實(shí)現(xiàn)如下:

// // 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 宏會(huì)把當(dāng)前類的 this 指針的值在 log 消息中顯示出來。RTC_LOG_T 宏基于 RTC_LOG 宏實(shí)現(xiàn)。
RTC_LOG_F 宏會(huì)把函數(shù)的函數(shù)名在 log 消息中顯示出來。
RTC_LOG_T_F 宏則會(huì)把當(dāng)前類的 this 指針和函數(shù)的函數(shù)名在 log 消息中都顯示出來。
在平臺(tái)支持 __PRETTY_FUNCTION__ 時(shí),RTC_LOG_F 和 RTC_LOG_T_F 宏還會(huì)以更漂亮的格式顯示函數(shù)名,即會(huì)連同函數(shù)的簽名一起來顯示函數(shù)名。RTC_LOG_F 和 RTC_LOG_T_F 宏都基于 RTC_LOG 宏實(shí)現(xiàn)。

RTC_LOG_V 宏與 RTC_LOG 宏基本相同,除了它的 severity 參數(shù)可以是一個(gè)變量外。RTC_LOG_V 宏與 RTC_LOG 宏都基于 RTC_LOG_FILE_LINE 宏實(shí)現(xiàn)。感覺這里的三個(gè)宏可以省掉一個(gè),讓 RTC_LOG 宏基于 RTC_LOG_V 宏實(shí)現(xiàn),如下面這樣:

#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 等類實(shí)現(xiàn)。

RTC_LOG_ERRNO 和 RTC_LOG_ERRNO_EX 宏還會(huì)在 log 消息中帶上一些錯(cuò)誤相關(guān)的信息。這兩個(gè)宏基于 RTC_LOG_E 宏實(shí)現(xiàn)。與 RTC_LOG_FILE_LINE 宏類似,RTC_LOG_E 也是基于 LogCall 和 LogStreamer 等類實(shí)現(xiàn)。

來看 RTC_LOG_FILE_LINE 宏的實(shí)現(xiàn):

#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 類只定義了一個(gè)成員函數(shù),即 operator&(),這個(gè)成員函數(shù)接收一個(gè) LogStreamer 參數(shù)。如 operator&() 成員函數(shù)的注釋的說明,這里重載的操作符不要求一定是 &,只要是一個(gè)比 << 操作符優(yōu)先級(jí)低的操作符即可。

站在 LogCall 類的角度看,RTC_LOG_FILE_LINE 宏完成的工作是:1. 創(chuàng)建一個(gè) LogCall 類的對(duì)象;2. 以動(dòng)態(tài)的方式創(chuàng)建一個(gè) LogStreamer 對(duì)象;3. 傳入創(chuàng)建的 LogStreamer 對(duì)象,在創(chuàng)建的 LogCall 類對(duì)象上調(diào)用其成員函數(shù)。

RTC_LOG_FILE_LINE 宏的意圖是,以動(dòng)態(tài)的方式創(chuàng)建一個(gè) LogStreamer 對(duì)象,并以該對(duì)象為參數(shù),調(diào)用某個(gè)函數(shù),被調(diào)用的這個(gè)函數(shù),把 LogStreamer 對(duì)象的 log 消息輸出出去。

把 RTC_LOG_FILE_LINE 宏換一種實(shí)現(xiàn)方式,來更清晰地看一下這個(gè)宏的實(shí)現(xiàn)。首先給 LogCall 類增加一個(gè)函數(shù):

template <typename... Ts>RTC_FORCE_INLINE void func(const LogStreamer<Ts...>& streamer) {streamer.Call();}

然后修改 RTC_LOG_FILE_LINE 宏的實(shí)現(xiàn)方式:

#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__)

注意,對(duì) LogCall 類的 operator&() 的調(diào)用被替換為了對(duì)成員函數(shù) func() 的調(diào)用。

經(jīng)過了這樣的修改,使用 RTC_LOG 的代碼也要做一些修改,如下:

RTC_LOG(INFO) << __FUNCTION__;RTC_LOG_HAN(INFO) << __FUNCTION__ << " HAN" );

注意,相對(duì)于原來的 RTC_LOG,修改之后的 RTC_LOG 宏在使用時(shí),需要在語句的最后添加一個(gè)右括號(hào)。使用時(shí)最后的右括號(hào)顯得非常奇怪。從中我們也可以體會(huì)一下 WebRTC 用 operator&() 實(shí)現(xiàn) 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_; };

對(duì) LogStreamer 模板類的 operator<<() 成員的連續(xù)調(diào)用,創(chuàng)建一個(gè) LogStreamer 模板類對(duì)象的單鏈表,創(chuàng)建的每個(gè)新的鏈表節(jié)點(diǎn)會(huì)被插入鏈表的頭部。也就是說,使用 RTC_LOG 宏時(shí),先傳入的參數(shù)所對(duì)應(yīng)的 LogStreamer 對(duì)象會(huì)位于鏈表的后面。鏈表的節(jié)點(diǎn)的值的類型為 Val:

template <LogArgType N, typename T> struct Val {static constexpr LogArgType Type() { return N; }T GetVal() const { return val; }T val; };

在 LogCall 類的 operator&() 函數(shù)中調(diào)用 LogStreamer 對(duì)象的 Call() 函數(shù),將調(diào)用最后創(chuàng)建的 LogStreamer 對(duì)象的 Call(),將觸發(fā)一系列的對(duì) LogStreamer 對(duì)象的 Call() 函數(shù)的調(diào)用,最終將調(diào)用 LogStreamer<> 的 Call() 函數(shù)。由模板類 LogStreamer 的 Call() 函數(shù)的實(shí)現(xiàn),可以知道,LogStreamer<> 的 Call() 函數(shù)將以使用 RTC_LOG 宏時(shí)參數(shù)傳入的先后順序接收各個(gè)參數(shù)。

LogStreamer<> 的 Call() 函數(shù)提取各個(gè)參數(shù)的類型,構(gòu)造類型數(shù)組,并將類型數(shù)組和各個(gè)參數(shù)的實(shí)際值傳給 Log() 來輸出 log。

Log() 函數(shù)的定義如下:

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_ANDROIDcase LogArgType::kLogMetadataTag: {const LogMetadataTag tag_meta = va_arg(args, LogMetadataTag);meta = {{nullptr, 0, tag_meta.severity}, ERRCTX_NONE, 0};tag = tag_meta.tag;break;} #endifdefault: {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() 函數(shù)構(gòu)造 LogMessage 對(duì)象,并將各個(gè)要輸出的 log 值送進(jìn) LogMessage 的 rtc::StringBuilder。

WebRTC 實(shí)現(xiàn)的 LogSink

默認(rèn)情況下,LogMessage 的 streams_ 中沒有注冊(cè)任何 LogSink,然而 WebRTC 實(shí)際上還是提供了幾個(gè) LogSink 的實(shí)現(xiàn)的。具體而言,是在 webrtc/src/rtc_base/logsinks.h 中,有兩個(gè) LogSink 的實(shí)現(xiàn),它們分別是類 FileRotatingLogSink 和 CallSessionFileRotatingLogSink。

FileRotatingLogSink 使用一個(gè) FileRotatingStream 把日志寫入一個(gè)支持 rotate 的文件。這個(gè)類的聲明如下:

// 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); };

這個(gè)類是對(duì) FileRotatingStream 的一個(gè)不是很復(fù)雜的封裝,它的實(shí)現(xiàn)如下:

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 在收到日志消息時(shí),簡(jiǎn)單地把日志消息寫入 FileRotatingStream。

CallSessionFileRotatingLogSink 與 FileRotatingLogSink 類似,僅有的區(qū)別是,它使用的是 CallSessionFileRotatingStream。這個(gè)類的聲明如下:

// 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 的實(shí)現(xiàn)如下:

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 系統(tǒng)實(shí)現(xiàn),還有幾點(diǎn)看得比較暈的地方:

  • LogStreamer 模板類的實(shí)現(xiàn)。LogStreamer 模板類的實(shí)現(xiàn)用了一些模板元編程的技巧,std::enable_if、std::decay 等 STL 的組件的使用讓人覺得有點(diǎn)不太容易懂。
  • 函數(shù)可變參數(shù)列表的實(shí)現(xiàn)原理。可變參數(shù)列表的使用不獨(dú)于此,其原理是需要明白的比較重要的一個(gè) C/C++ 開發(fā)的技術(shù)點(diǎn)。

關(guān)于 WebRTC 的 rtc_base log 系統(tǒng)實(shí)現(xiàn):

  • log 系統(tǒng)除了要功能強(qiáng)大,性能優(yōu)異,還需要提供一個(gè)非常好用的用戶接口。易用的用戶接口的重要性,從這個(gè) log 系統(tǒng)復(fù)雜的 LogStreamer 模板類實(shí)現(xiàn)就可見一斑。
  • WebRTC 的 rtc_base log 系統(tǒng)實(shí)現(xiàn)在向 LogSink 輸出 log 時(shí),使用了鎖做同步,log 輸出非常頻繁時(shí),鎖爭(zhēng)搶可能會(huì)成為一個(gè)問題。
  • 輸出 log 一般都需要執(zhí)行 IO 操作,不過 WebRTC 的 rtc_base log 系統(tǒng)實(shí)現(xiàn)沒有把 log 輸出異步化,輸出 log 都是同步操作,即使專門實(shí)現(xiàn)的 LogSink FileRotatingLogSink 和 CallSessionFileRotatingLogSink 也是。

Done.WebRTC 有多套 log 輸出系統(tǒng),一套位于 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 輸出系統(tǒng)。它可以非常方便地輸出類似于下面這種格式的 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 內(nèi)容信息。

整體來看,這個(gè) log 系統(tǒng)可以分為四層:對(duì)于 log 系統(tǒng)的用戶,看到的是用于輸出不同重要性的 log 的一組宏,如 RTC_LOG(sev),這組宏提供了類似于輸出流 std::ostream 的接口來輸出 log;log 系統(tǒng)的核心是 LogMessage,它用于對(duì)要輸出的 log 做格式化,產(chǎn)生最終要輸出的字符串,并把產(chǎn)生的字符串送給 log 后端完成 log 輸出,同時(shí)還可以通過它對(duì) log 系統(tǒng)的行為施加控制;最下面的是 log 后端,log 后端用 LogSink 接口描述;在 RTC_LOG(sev) 等宏和 LogMessage 之間的是用于實(shí)現(xiàn) log 系統(tǒng)的用戶看到的類似于輸出流的接口的模板類 LogStreamer 和 LogCall 等。這個(gè) log 系統(tǒng)整體如下圖所示:

Log 后端

Log 后端用 LogSink 接口描述,這個(gè)接口聲明(位于 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 的兩個(gè)非純虛函數(shù)實(shí)現(xiàn)如下(位于 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 系統(tǒng)的全局控制

LogMessage 可以用于對(duì) log 系統(tǒng)施加一些全局性的控制,這主要是指如下這些全局性的狀態(tài):

  • log 輸出的最低 severity,通過 g_min_sev 控制;
  • debug 輸出的最低 severity,通過 g_dbg_sev 控制;
  • 是否要向標(biāo)準(zhǔn)錯(cuò)誤輸出輸出 log,通過 log_to_stderr_ 開關(guān)控制;
  • 最終輸出的日志信息中是否要包含線程信息和時(shí)間戳,通過 thread_ 和 timestamp_ 開關(guān)控制;
  • 要輸出的 log 后端 streams_。

這里先看一下這套 log 系統(tǒng)支持的 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 系統(tǒng)包括 LS_NONE 支持 5 級(jí) severity,各級(jí) severity 的使用說明,如上面代碼中的注釋。

LogMessage 用一個(gè)靜態(tài)的 StreamList 保存 log 后端 LogSink:

private: . . . . . .typedef std::pair<LogSink*, LoggingSeverity> StreamAndSeverity;typedef std::list<StreamAndSeverity> StreamList; . . . . . .static StreamList streams_;

LogMessage 用一個(gè)列表記錄 LogSink 對(duì)象的指針及其期望接收的 log 的最低的 severity。

LogMessage 提供了一些接口來訪問這些全局狀態(tài)。

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() 可以用于設(shè)置 log_to_stderr_ 格式化開關(guān):

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 的狀態(tài)由各個(gè) LogSink 接收 log 的最低 severity 和 g_dbg_sev 的狀態(tài)共同決定,具體來說,它是 g_dbg_sev 和各個(gè) 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è)函數(shù)用于訪問 g_min_sev:

bool LogMessage::Loggable(LoggingSeverity sev) {return sev >= g_min_sev; }int LogMessage::GetMinLogSeverity() {return g_min_sev; }

LogThreads()/LogTimestamps() 可以用于設(shè)置 thread_ 和 timestamp_ 格式化開關(guān):

void LogMessage::LogThreads(bool on) {thread_ = on; }void LogMessage::LogTimestamps(bool on) {timestamp_ = on; }

除了上面這些專用的控制接口之外,LogMessage 還提供了另一個(gè)函數(shù) ConfigureLogging() 對(duì) log 系統(tǒng)進(jìn)行控制:

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 featuresif (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è)函數(shù)傳入一個(gè)字符串,同時(shí)修改 g_dbg_sev、thread_ 和 timestamp_ 等多個(gè)狀態(tài)。

g_dbg_sev、log_to_stderr_ 和 g_min_sev 之間的關(guān)系:g_min_sev 定義了 log 系統(tǒng)輸出的 log 的最低 severity,當(dāng)要輸出的 log 的 severity 低于 g_min_sev 時(shí),LogMessage 會(huì)認(rèn)為它是不需要輸出的。debug log 輸出可以看作是一個(gè)特殊的封裝了向標(biāo)準(zhǔn)錯(cuò)誤輸出或本地系統(tǒng)特定的 log 輸出系統(tǒng)打印 log 的 LogSink,g_dbg_sev 用于控制向這個(gè) LogSink 中輸出的 log 的最低 severity,而 log_to_stderr_ 則用于控制是否最終把 log 輸出到標(biāo)準(zhǔn)錯(cuò)誤輸出。

對(duì)于上述操作全局狀態(tài)的函數(shù)做一下總結(jié),如下表所示:

全局狀態(tài)訪問接口說明
g_dbg_sevLogToDebug()/GetLogToDebug()debug 輸出的最低 severity
log_to_stderr_SetLogToStderr()是否要向標(biāo)準(zhǔn)錯(cuò)誤輸出輸出 log
streams_GetLogToStream()/AddLogToStream()/RemoveLogToStream()要輸出的 log 后端
g_min_sevGetMinLogSeverity()/IsNoop()/Loggable()狀態(tài)由各個(gè) LogSink 接收 log 的最低 severity 和 g_dbg_sev 的狀態(tài)共同決定
thread_LogThreads()最終輸出的日志信息中是否要包含線程信息
timestamp_LogTimestamps()最終輸出的日志信息中是否要包含時(shí)間戳

輸出 log

這個(gè) log 系統(tǒng)輸出的完整 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

其中包含了時(shí)間戳和線程號(hào)。

LogMessage 對(duì)象持有一個(gè) rtc::StringBuilder 類型的對(duì)象 print_stream_,要最終輸出的內(nèi)容,都會(huì)先被送進(jìn) print_stream_。LogMessage 對(duì)象構(gòu)造時(shí),根據(jù)上面介紹的全局性的控制狀態(tài),向 print_stream_ 輸出 log header,主要包括 log 輸出的文件名和行號(hào),可能包括時(shí)間戳和線程號(hào)。之后 LogMessage 的使用者可以獲得它的 rtc::StringBuilder,并向其中輸出任何數(shù)量和格式的 log 信息。在 LogMessage 對(duì)象析構(gòu)時(shí),會(huì)從 print_stream_ 獲得字符串,并把字符串輸出到 debug log,或者外部注冊(cè)的 LogSink 中。

LogMessage 對(duì)象的構(gòu)造過程如下:

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 << "): "; #elseprint_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_WINcase 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() 用于記錄或獲得首次日志時(shí)間,WallClockStartTime() 則似乎沒有看到其應(yīng)用場(chǎng)合:

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 消息中的時(shí)間戳是相對(duì)時(shí)間,而不是絕對(duì)的墻上時(shí)間。

FilenameFromPath() 函數(shù)用于從文件的絕對(duì)路徑中提取文件名:

// 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;elsereturn (end1 > end2) ? end1 + 1 : end2 + 1; }

LogMessage 的使用者可以通過其 stream() 函數(shù)獲得其 rtc::StringBuilder:

rtc::StringBuilder& LogMessage::stream() {return print_stream_; }

LogMessage 對(duì)象的析構(gòu)過程如下:

LogMessage::~LogMessage() {FinishPrintStream();const std::string str = print_stream_.Release();if (severity_ >= g_dbg_sev) { #if defined(WEBRTC_ANDROID)OutputToDebug(str, severity_, tag_); #elseOutputToDebug(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_); #elsekv.first->OnLogMessage(str, severity_); #endif}} }

在要輸出的 log 的 severity 高于 g_dbg_sev 時(shí),LogMessage 對(duì)象的析構(gòu)函數(shù)會(huì)將 log 輸出到 debug;LogMessage 對(duì)象的析構(gòu)函數(shù)還會(huì)把 log 輸出到所有其請(qǐng)求的最低 severity 低于當(dāng)前這條 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) { #endifbool 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_ANDROIDif (log_to_stderr) {fprintf(stderr, "%s", str.c_str());fflush(stderr);} }

OutputToDebug() 將日志輸出到本地系統(tǒng)特定的 log 系統(tǒng)或標(biāo)準(zhǔn)錯(cuò)誤輸出,如 Android 的 logcat。前面介紹的 log_to_stderr_ 開關(guān)在這個(gè)函數(shù)中起作用。

Log 輸出用戶接口及其實(shí)現(xiàn)

這套 log 系統(tǒng)的用戶一般不會(huì)直接使用 LogMessage 打 log,而是會(huì)使用基于 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 系統(tǒng)的用戶使用的宏主要有 RTC_DLOG* 系列和 RTC_LOG* 系列。RTC_DLOG 宏與他們對(duì)應(yīng)的 RTC_LOG 宏是等價(jià)的,除了它們只在 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 平臺(tái)有一個(gè)平臺(tái)專用的可以帶上 TAG 的宏:

#ifdef WEBRTC_ANDROIDnamespace 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 平臺(tái)也有一些平臺(tái)專用的宏:

#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 系列宏的實(shí)現(xiàn)如下:

// // 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 宏會(huì)把當(dāng)前類的 this 指針的值在 log 消息中顯示出來。RTC_LOG_T 宏基于 RTC_LOG 宏實(shí)現(xiàn)。
RTC_LOG_F 宏會(huì)把函數(shù)的函數(shù)名在 log 消息中顯示出來。
RTC_LOG_T_F 宏則會(huì)把當(dāng)前類的 this 指針和函數(shù)的函數(shù)名在 log 消息中都顯示出來。
在平臺(tái)支持 __PRETTY_FUNCTION__ 時(shí),RTC_LOG_F 和 RTC_LOG_T_F 宏還會(huì)以更漂亮的格式顯示函數(shù)名,即會(huì)連同函數(shù)的簽名一起來顯示函數(shù)名。RTC_LOG_F 和 RTC_LOG_T_F 宏都基于 RTC_LOG 宏實(shí)現(xiàn)。

RTC_LOG_V 宏與 RTC_LOG 宏基本相同,除了它的 severity 參數(shù)可以是一個(gè)變量外。RTC_LOG_V 宏與 RTC_LOG 宏都基于 RTC_LOG_FILE_LINE 宏實(shí)現(xiàn)。感覺這里的三個(gè)宏可以省掉一個(gè),讓 RTC_LOG 宏基于 RTC_LOG_V 宏實(shí)現(xiàn),如下面這樣:

#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 等類實(shí)現(xiàn)。

RTC_LOG_ERRNO 和 RTC_LOG_ERRNO_EX 宏還會(huì)在 log 消息中帶上一些錯(cuò)誤相關(guān)的信息。這兩個(gè)宏基于 RTC_LOG_E 宏實(shí)現(xiàn)。與 RTC_LOG_FILE_LINE 宏類似,RTC_LOG_E 也是基于 LogCall 和 LogStreamer 等類實(shí)現(xiàn)。

來看 RTC_LOG_FILE_LINE 宏的實(shí)現(xiàn):

#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 類只定義了一個(gè)成員函數(shù),即 operator&(),這個(gè)成員函數(shù)接收一個(gè) LogStreamer 參數(shù)。如 operator&() 成員函數(shù)的注釋的說明,這里重載的操作符不要求一定是 &,只要是一個(gè)比 << 操作符優(yōu)先級(jí)低的操作符即可。

站在 LogCall 類的角度看,RTC_LOG_FILE_LINE 宏完成的工作是:1. 創(chuàng)建一個(gè) LogCall 類的對(duì)象;2. 以動(dòng)態(tài)的方式創(chuàng)建一個(gè) LogStreamer 對(duì)象;3. 傳入創(chuàng)建的 LogStreamer 對(duì)象,在創(chuàng)建的 LogCall 類對(duì)象上調(diào)用其成員函數(shù)。

RTC_LOG_FILE_LINE 宏的意圖是,以動(dòng)態(tài)的方式創(chuàng)建一個(gè) LogStreamer 對(duì)象,并以該對(duì)象為參數(shù),調(diào)用某個(gè)函數(shù),被調(diào)用的這個(gè)函數(shù),把 LogStreamer 對(duì)象的 log 消息輸出出去。

把 RTC_LOG_FILE_LINE 宏換一種實(shí)現(xiàn)方式,來更清晰地看一下這個(gè)宏的實(shí)現(xiàn)。首先給 LogCall 類增加一個(gè)函數(shù):

template <typename... Ts>RTC_FORCE_INLINE void func(const LogStreamer<Ts...>& streamer) {streamer.Call();}

然后修改 RTC_LOG_FILE_LINE 宏的實(shí)現(xiàn)方式:

#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__)

注意,對(duì) LogCall 類的 operator&() 的調(diào)用被替換為了對(duì)成員函數(shù) func() 的調(diào)用。

經(jīng)過了這樣的修改,使用 RTC_LOG 的代碼也要做一些修改,如下:

RTC_LOG(INFO) << __FUNCTION__;RTC_LOG_HAN(INFO) << __FUNCTION__ << " HAN" );

注意,相對(duì)于原來的 RTC_LOG,修改之后的 RTC_LOG 宏在使用時(shí),需要在語句的最后添加一個(gè)右括號(hào)。使用時(shí)最后的右括號(hào)顯得非常奇怪。從中我們也可以體會(huì)一下 WebRTC 用 operator&() 實(shí)現(xiàn) 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_; };

對(duì) LogStreamer 模板類的 operator<<() 成員的連續(xù)調(diào)用,創(chuàng)建一個(gè) LogStreamer 模板類對(duì)象的單鏈表,創(chuàng)建的每個(gè)新的鏈表節(jié)點(diǎn)會(huì)被插入鏈表的頭部。也就是說,使用 RTC_LOG 宏時(shí),先傳入的參數(shù)所對(duì)應(yīng)的 LogStreamer 對(duì)象會(huì)位于鏈表的后面。鏈表的節(jié)點(diǎn)的值的類型為 Val:

template <LogArgType N, typename T> struct Val {static constexpr LogArgType Type() { return N; }T GetVal() const { return val; }T val; };

在 LogCall 類的 operator&() 函數(shù)中調(diào)用 LogStreamer 對(duì)象的 Call() 函數(shù),將調(diào)用最后創(chuàng)建的 LogStreamer 對(duì)象的 Call(),將觸發(fā)一系列的對(duì) LogStreamer 對(duì)象的 Call() 函數(shù)的調(diào)用,最終將調(diào)用 LogStreamer<> 的 Call() 函數(shù)。由模板類 LogStreamer 的 Call() 函數(shù)的實(shí)現(xiàn),可以知道,LogStreamer<> 的 Call() 函數(shù)將以使用 RTC_LOG 宏時(shí)參數(shù)傳入的先后順序接收各個(gè)參數(shù)。

LogStreamer<> 的 Call() 函數(shù)提取各個(gè)參數(shù)的類型,構(gòu)造類型數(shù)組,并將類型數(shù)組和各個(gè)參數(shù)的實(shí)際值傳給 Log() 來輸出 log。

Log() 函數(shù)的定義如下:

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_ANDROIDcase LogArgType::kLogMetadataTag: {const LogMetadataTag tag_meta = va_arg(args, LogMetadataTag);meta = {{nullptr, 0, tag_meta.severity}, ERRCTX_NONE, 0};tag = tag_meta.tag;break;} #endifdefault: {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() 函數(shù)構(gòu)造 LogMessage 對(duì)象,并將各個(gè)要輸出的 log 值送進(jìn) LogMessage 的 rtc::StringBuilder。

WebRTC 實(shí)現(xiàn)的 LogSink

默認(rèn)情況下,LogMessage 的 streams_ 中沒有注冊(cè)任何 LogSink,然而 WebRTC 實(shí)際上還是提供了幾個(gè) LogSink 的實(shí)現(xiàn)的。具體而言,是在 webrtc/src/rtc_base/logsinks.h 中,有兩個(gè) LogSink 的實(shí)現(xiàn),它們分別是類 FileRotatingLogSink 和 CallSessionFileRotatingLogSink。

FileRotatingLogSink 使用一個(gè) FileRotatingStream 把日志寫入一個(gè)支持 rotate 的文件。這個(gè)類的聲明如下:

// 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); };

這個(gè)類是對(duì) FileRotatingStream 的一個(gè)不是很復(fù)雜的封裝,它的實(shí)現(xiàn)如下:

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 在收到日志消息時(shí),簡(jiǎn)單地把日志消息寫入 FileRotatingStream。

CallSessionFileRotatingLogSink 與 FileRotatingLogSink 類似,僅有的區(qū)別是,它使用的是 CallSessionFileRotatingStream。這個(gè)類的聲明如下:

// 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 的實(shí)現(xiàn)如下:

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 系統(tǒng)實(shí)現(xiàn),還有幾點(diǎn)看得比較暈的地方:

  • LogStreamer 模板類的實(shí)現(xiàn)。LogStreamer 模板類的實(shí)現(xiàn)用了一些模板元編程的技巧,std::enable_if、std::decay 等 STL 的組件的使用讓人覺得有點(diǎn)不太容易懂。
  • 函數(shù)可變參數(shù)列表的實(shí)現(xiàn)原理。可變參數(shù)列表的使用不獨(dú)于此,其原理是需要明白的比較重要的一個(gè) C/C++ 開發(fā)的技術(shù)點(diǎn)。

關(guān)于 WebRTC 的 rtc_base log 系統(tǒng)實(shí)現(xiàn):

  • log 系統(tǒng)除了要功能強(qiáng)大,性能優(yōu)異,還需要提供一個(gè)非常好用的用戶接口。易用的用戶接口的重要性,從這個(gè) log 系統(tǒng)復(fù)雜的 LogStreamer 模板類實(shí)現(xiàn)就可見一斑。
  • WebRTC 的 rtc_base log 系統(tǒng)實(shí)現(xiàn)在向 LogSink 輸出 log 時(shí),使用了鎖做同步,log 輸出非常頻繁時(shí),鎖爭(zhēng)搶可能會(huì)成為一個(gè)問題。
  • 輸出 log 一般都需要執(zhí)行 IO 操作,不過 WebRTC 的 rtc_base log 系統(tǒng)實(shí)現(xiàn)沒有把 log 輸出異步化,輸出 log 都是同步操作,即使專門實(shí)現(xiàn)的 LogSink FileRotatingLogSink 和 CallSessionFileRotatingLogSink 也是。

Done.

總結(jié)

以上是生活随笔為你收集整理的WebRTC 的 log 系统实现分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。