WebRTC源码分析之日志-RTC_LOG

文章目录

        • RTC_LOG使用示例
          • 示例-日志输出到标准错误stderr
          • 示例-带有时间和线程信息的日志
          • 示例-日志输入到文件
          • 示例-带有错误信息的日志
        • RTC_LOG源码分析
          • LogMessage类
            • 数据成员
            • 构造器
            • 析构器
            • 更新最小日志级别
            • 配置数据
            • 获取数据
            • 添加、删除文件流
          • Log函数
          • LogCall类
          • LogStreamer类
          • RTC_LOG宏函数
            • 宏函数原型
            • 宏函数的使用
          • RTC_LOG_ERRNO宏函数
            • 宏函数原型
            • 宏函数的使用
          • LogSink类
          • FileRotatingLogSink类
          • CallSessionFileRotatingLogSink类
        • 小结

WebRTC提供的日志打印函数,默认是输出到标准错误的。也可以自己提供文件流,注册到日志处理含中,用于接收日志。日志是分等级的,每个接收数据的流(包括标准错误)都有一个等级,当日志等级不小于该等级时才会输入到文件流。同一条日志可以输出到多个文件流。

RTC_LOG使用示例

工程

先创建一个工程用于运行示例。如何创建工程,参考《WebRTC源码分析之工程-project》,在src\examples\BUILD.gn中添加如下内容:

rtc_executable("webrtc_learn"){
    testonly = true
  sources = [
    "webrtclearn/main.cc"         
  ]
  deps = [
    "../rtc_base:rtc_base"       
  ]
}
示例-日志输出到标准错误stderr
#include "rtc_base/logging.h"

int main()
{
    RTC_LOG(INFO) << "hello world";    

    return 0;
}

在这里插入图片描述

日志默认是输出到标准错误stderr

示例-带有时间和线程信息的日志
#include "rtc_base/logging.h"
#include 

int main()
{
    /*显示时间戳*/
    rtc::LogMessage::LogTimestamps(true); 

    /*显示线程id*/
    rtc::LogMessage::LogThreads(true);

    RTC_LOG(INFO) << "hello world";    

    Sleep(1);

    RTC_LOG(INFO) << "China is great";

    return 0;
}

在这里插入图片描述

显示的时间从第一条日志开始计时,也可以程序的开始主动调用rtc::LogMessage::LogStartTime()开始计时。

示例-日志输入到文件
#include "rtc_base/logging.h"
#include "rtc_base/log_sinks.h"

using namespace rtc;
int main()
{
    /*日志默认是输出到标准错误的,不调用也是可以的。*/
    LogMessage::SetLogToStderr(true);

    /*创建日志文件*/
    FileRotatingLogSink frls("C:\\Users\\study\\Desktop\\log","webrtc_log",1024,2);
    frls.Init();

    /*将日志输出到日志文件中,接收WARNING及以上级别的日志。*/
    LogMessage::AddLogToStream(&frls, WARNING);
    
    RTC_LOG(INFO) << "information";    
    RTC_LOG(WARNING) << "warning";
    RTC_LOG(LERROR) << "error";

    return 0;
}

在这里插入图片描述
WebRTC源码分析之日志-RTC_LOG_第1张图片
日志是分等级的,在创建文件流的时候,可以指定接收日志的最低等级。文件中没有接收 RTC_LOG(INFO) << “information”;这条日志。

日志默认输出到标准错误,但可以通过SetLogToStderr()设置是否向标准错误输出。

示例-带有错误信息的日志
#include "rtc_base/logging.h"
#include 

int main()
{
    /*函数执行出错设置该全局变量*/
    errno = 6;

    RTC_LOG_ERRNO(INFO) << "hello world";

    return 0;
}

在这里插入图片描述
输出的日志带有错误值,及错误信息。

RTC_LOG源码分析

RTC_LOG源码所在的文件位置:src\rtc_base\logging.h logging.cc

RTC_LOGRTC_DCHECK的设计是类似的,很多代码也是重复的。在RTC_DCHECK中介绍过的,在这里就不做介绍了。

LogMessage类

LogMessage类所在的文件位置:src\rtc_base\logging.h logging.cc

LogMessage类用于接收日志数据,将数据写入标准错误日志文件LogMessage类的

绝大部分数据成员和函数成员都是静态的,可以保存全局信息。

数据成员
typedef std::pair<LogSink*, LoggingSeverity> StreamAndSeverity;
typedef std::list<StreamAndSeverity> StreamList;

static StreamList streams_;
static bool thread_, timestamp_;
static bool log_to_stderr_;

以上的数据均是静态数据成员,相当于类内部的全局变量。

streams_中保存的是文件流,每个文件流都有自己的等级,在输出日志的时候,只要日志等级大于或等于文件流的等级,日志信息就写入文件流中。可以有多个文件流同时接收日志数据。接收日志的文件流需要继承自LogSink类,并覆写OnLogMessage()函数。

thread_timestamp_分别记录着在打印日志时,是否输出线程id,是否打印时间信息。

log_to_stderr_用于标识是否将日志数据打印到标准错误,默认值是true,表示需要将日志信息输出到标准错误。

rtc::StringBuilder print_stream_;
LoggingSeverity severity_;
std::string extra_;

每条日志都会生成一个LogMessage类对象,用于处理日志。

print_stream_:所有的日志数据都会格式化成字符串,保存在这个变量中。

severity_:记录日志的等级

extra_:若需要输出错误信息,需要将错误信息格式化成字符串保存这个变量中。

#if !defined(NDEBUG)
static LoggingSeverity g_min_sev = LS_INFO;
static LoggingSeverity g_dbg_sev = LS_INFO;
#else
static LoggingSeverity g_min_sev = LS_NONE;
static LoggingSeverity g_dbg_sev = LS_NONE;
#endif

g_min_sevg_dbg_sev是全局变量,分别保存着输出日志的最小级别和调试时输出的最小日志级别。

构造器
/*获取日志开始时间*/
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;
}

这两个函数用于返回日志开始的时间,一个是相对时间,一个是绝对时间。函数的内部均有一个局部变量用于记录开始的时间,在首次调用函数的时候,才会对局部变量初始化,之后就不再初始化了,这样就记录了首次调用的时间,也就是日志开始的时间。

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_) 
  {
    int64_t time = TimeDiff(SystemTimeMillis(), 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) 
  {
    /*文件名只打印名字不打印路径*/
    print_stream_ << "(" << FilenameFromPath(file) << ":" << line << "): ";
  }

  /*输出错误值,及其对应的错误信息。*/
  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;
      default:
        break;
    }

    /*保存的是与错误相关的信息*/
    extra_ = tmp.str();
  }
}

每条待输出的日志,都会单独生成一个LogMessage类对象,用于处理这条日志。

print_stream_中保存着格式化的日志数据,要输出的日志数据,经过格式化后,存放到这个变量。处理完所有的日志数据后,将print_stream_一次性输出。

调用WallClockStartTime()函数并没有用于获取时间,而是当主程序中没有主动调用初始化时间时,在输出第一条日志时初始化时间。

在构建LogMessage类时,需要根据数据成员而决定是否显示日志的时间、线程id等信息。

析构器

因为一条日志用一个单独的LogMessage类对象处理,在处理日志的过程中,所有需要打印的数据都会被格式化成字符串保存在类对象中。在LogMessage类对象析构时,需要将日志数据输出到指定文件流或标准错误。

void LogMessage::FinishPrintStream() 
{
  if (!extra_.empty())
    print_stream_ << " : " << extra_;

  print_stream_ << "\n";
}

将错误信息添加到print_stream_中,此时print_stream_中保存着,日志数据经格式化后的所有数据。

/*向标准错误输出日志*/
void LogMessage::OutputToDebug(const std::string& str,LoggingSeverity severity) 
{
  bool log_to_stderr = log_to_stderr_;

  if (log_to_stderr) 
  {
    fprintf(stderr, "%s", str.c_str());      /*打印日志*/
    fflush(stderr);
  }
}

根据log_to_stderr_的值,将日志数据输出到标准错误。

LogMessage::~LogMessage() 
{
  /*添加错误信息*/
  FinishPrintStream();

  const std::string str = print_stream_.Release();

  /*日志的级别大于调试的最小日志级别*/
  if (severity_ >= g_dbg_sev) 
  {
    OutputToDebug(str, severity_);     /*向标准错误输出日志*/
  }

  /*上锁访问stream_*/
  CritScope cs(&g_log_crit);    
  for (auto& kv : streams_)     /*向指定的流中输出日志*/
  {
    if (severity_ >= kv.second) 
    {
      kv.first->OnLogMessage(str, severity_);
    }
  }
}

每文件流都有自己的最小日志级别,而g_dbg_sev保存的最小日志级别用于控制标准错误的最小日志级别。当日志的级别小于g_dbg_sev时,就不再向标准错误输出了。

在析构器中先是将日志输出到标准错误,然后遍历streams_保存的数据流,若日志级别不小于文件流级别,则将日志输出到流文件。streams_可以保存多个文件流,一条日志可以写入多个文件。

streams_存在竞争冒险,在一个线程添加文件流时,另一个线程就不可以访问streams_。每个线程修改或访问streams_时,都需要独占streams_,所以需要加锁。

更新最小日志级别
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;
}

从所有的文件流和标准错误中,获取最小的日志级别。在处理日志时,若日志的级别小于最小日志级别,则该日志不需要打印,放弃处理。

配置数据
/*设置是否显示线程id*/
void LogMessage::LogThreads(bool on) 
{
  thread_ = on;
}

/*设置是否显示时间信息*/
void LogMessage::LogTimestamps(bool on) 
{
  timestamp_ = on;
}

/*设置调试日志的最低级别*/
void LogMessage::LogToDebug(LoggingSeverity min_sev) 
{
  g_dbg_sev = min_sev;
  CritScope cs(&g_log_crit);   
  UpdateMinLogSeverity();
}

/*设置是否将日志打印到标准错误流*/
void LogMessage::SetLogToStderr(bool log_to_stderr) 
{
  log_to_stderr_ = log_to_stderr;
}

这些函数很简单,注释已经解释的很清楚了。

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;
    if (token == "tstamp") 
    {
      LogTimestamps();    /*显示时间*/
    } else if (token == "thread")
    {
      LogThreads();      /*显示线程id*/
    } 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;
    } else if (token == "debug") 
    {
      debug_level = current_level;   /*更改调试时日志最小级别*/
    }
  }

  LogToDebug(debug_level);
}

通过字符串的方式配置LogMessagetokenize()函数会把字符串按照空格进行分割,分割后的字符串存放在vector中。

使用示例如下:

#include "rtc_base/logging.h"

int main() 
{
  char buf[] = "tstamp thread warning debug";

  rtc::LogMessage::ConfigureLogging(buf);

  RTC_LOG(INFO) << "information";     /*级别太低,不会被打印。*/
  RTC_LOG(WARNING) << "warning";
  RTC_LOG(LERROR) << "error";

  return 0;
}

在这里插入图片描述

配置的日志显示时间、线程id,并且只打印WARNING级别及以上级别的日志。

获取数据
/*返回流对象*/
rtc::StringBuilder& LogMessage::stream() 
{
  return print_stream_;
}

/*日志的最小级别*/
int LogMessage::GetMinLogSeverity() 
{
  return g_min_sev;
}

/*调试时日志可输出的最小级别*/
LoggingSeverity LogMessage::GetLogToDebug() 
{
  return g_dbg_sev;
}

/*获取指定流的等级,若为nullptr则返回所有流中最小级别。*/
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;
}

/*判断severity级别日志是否可以打印*/
bool LogMessage::IsNoop(LoggingSeverity severity) 
{
  /*若日志级别大于规定的级别,则返回false。*/
  if (severity >= g_dbg_sev || severity >= g_min_sev)
    return false;

  CritScope cs(&g_log_crit);
  if (streams_.size() > 0)
    return false;

  return true;  
}
添加、删除文件流
/*添加接收日志的文件流*/
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();
}

添加或删除文件流后,需要更新日志的最小级别,可能删除的流就是唯一的最小,所以需要及时更新。

Log函数
void Log(const LogArgType* fmt, ...) 
{
  va_list args;
  va_start(args, fmt);

  /*变参args的第一个参数是LogMetadata或LogMetadataErr*/
  LogMetadataErr meta;
  const char* tag = nullptr;
  switch (*fmt) 
  {
    case LogArgType::kLogMetadata: 
    {
      /*把LogMetadata包装成LogMetadataErr*/
      meta = {va_arg(args, LogMetadata), ERRCTX_NONE, 0};   /*注意ERRCTX_NONE*/
      break;
    }
    case LogArgType::kLogMetadataErr: 
    {
      meta = va_arg(args, LogMetadataErr);
      break;
    }
    default: 
    {
      RTC_NOTREACHED();    /*断言失败,结束进程。*/
      va_end(args);
      return;
    }
  }

  /*meta.meta.Severity()获取日志等级,判断是否打印该日志。*/
  if (LogMessage::IsNoop(meta.meta.Severity())) 
  {
    va_end(args);
    return;
  }

  /*根据LogMetadataErr中的数据构建LogMessage*/
  LogMessage log_message(meta.meta.File(), meta.meta.Line(),meta.meta.Severity(), meta.err_ctx, meta.err);

  /*将日志数据流入到log_message对象中*/
  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: 
      {
        const char* s = va_arg(args, const char*);
        log_message.stream() << (s ? s : "(null)");
        break;
      }
      case LogArgType::kStdString:
        log_message.stream() << *va_arg(args, const std::string*);
        break;
      case LogArgType::kStringView:
        log_message.stream() << *va_arg(args, const absl::string_view*);
        break;
      case LogArgType::kVoidP:
        log_message.stream() << rtc::ToHex(
            reinterpret_cast<uintptr_t>(va_arg(args, const void*)));
        break;
      default:
        RTC_NOTREACHED();
        va_end(args);
        return;
    }
  }

  va_end(args);

  /*log_message对象离开作用域,调用析构器,在析构器中打印日志数据。*/
}

调用全局函数Log()时,RTC_LOG中数据全部以变参的方式传入至args中,变参中数据的类型保存在fmt数组。变参args中的第一个参数是kLogMetadata类型或kLogMetadataErr类型,若是kLogMetadata类型则转成kLogMetadataErr类。

再根据kLogMetadataErr生成LogMessage对象,之后会将变参args中的数据,格式化成字符串,保存在LogMessage对象中,在这个函数最后,LogMessage对象离开作用域时,调用析构函数,在析构函数中将日志数据输出到文件中。

log_message.stream() << va_arg(args, int);中,log_message.stream()将返回print_stream_va_arg(args, int)将从变参中读取一个int类型数据。print_stream_StringBuilder类对象,底层重载了operator<<()可以将数据以字符串的方式保存到print_stream_中。

LogCall类

LogCall类所在的文件位置:src\rtc_base\logging.h

class LogCall final 
{
 public:
  template <typename... Ts>
  RTC_FORCE_INLINE void operator&(const LogStreamer<Ts...>& streamer) 
  {
    streamer.Call(); 
  }
};

LogCall类重载了&运算符,这个运算符接收LogStreamer类对象,并调用其Call()函数。

LogCall类相当于递归处理的终止条件,到达本类后,开始递归返回。

LogStreamer类

LogStreamer类在RTC_DCHECK中的介绍过,在处理完所有变参后,RTC_DCHECK中会调用FatalLog()函数,将所有参数传至这个函数进行下一步处理。此处会调用Log()函数进一步处理所有参数。有关LogStreamer的更多介绍,参看《WebRTC源码分析之断言-RTC_DCHECK》

RTC_LOG宏函数
宏函数原型
#define RTC_LOG(sev) RTC_LOG_FILE_LINE(rtc::sev, __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)
宏函数的使用
RTC_LOG(INFO) << "hello world"; 

将宏展开后:

rtc::webrtc_logging_impl::LogCall() & rtc::webrtc_logging_impl::LogStreamer<>() << rtc::webrtc_logging_impl::LogMetadata(__FILE__, __LINE__, INFO) << "hello world";

去掉命名空间,简化后:

LogCall() & LogStreamer<>() << LogMetadata(__FILE__, __LINE__, INFO) << "hello world";

<<运算符的优先级比&运算符高,且<<运算符的结合性是从左到右。LogMetadata(__FILE__, __LINE__, INFO)将定义一个LogMetadata对象,LogStreamer<>对象会把后面的两个参数一层一层的包裹起来,返回一个新的LogStreamer对象,作为参数传递至LogCall()临时对象的operator&()函数作为参数。然后递归的调用LogStreamer对象的Call()函数,将LogMetadata(__FILE__, __LINE__, INFO)对象和"hello world"组成变参,最终传递至全局函数Log()中,进行下一步的处理。

<<运算符&运算符的处理过程描述的很简单,在《WebRTC源码分析之断言-RTC_DCHECK》有更为详细的描述。

RTC_LOG_ERRNO宏函数
宏函数原型
#define RTC_LOG_ERRNO_EX(sev, err) RTC_LOG_E(sev, ERRNO, err)
#define RTC_LOG_ERRNO(sev) RTC_LOG_ERRNO_EX(sev, errno)

#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)      \
    }
宏函数的使用
RTC_LOG_ERRNO(INFO) << "hello world";

将宏展开后:

rtc::webrtc_logging_impl::LogCall() & rtc::webrtc_logging_impl::LogStreamer<>() << rtc::webrtc_logging_impl::LogMetadataErr {{__FILE__, __LINE__, rtc::INFO}, rtc::ERRCTX_ERRNO, (errno)} << "hello world";

去掉命名空间,简化后:

LogCall() & LogStreamer<>() << LogMetadataErr {{__FILE__, __LINE__, INFO},ERRCTX_ERRNO, (errno)} << "hello world";

这条宏展开后和上面宏大体相同,只是这里传入Log()函数的变参,第一个参数是LogMetadataErr类型,携带着错误信息。

LogSink类

LogSink类所在的文件位置:src\rtc_base\logging.h logging.cc

class LogSink 
{
 public:
  LogSink() {}
  virtual ~LogSink() {}
  virtual void OnLogMessage(const std::string& message) = 0;   
};

LogSink类是一个接口类,所有需要接收日志的文件流都需要继承自此类,并覆写OnLogMessage()函数。参数message保存的是一条完整的日志,将message写入文件即可。

FileRotatingLogSink类

FileRotatingLogSink类所在的文件位置:src\rtc_base\log_sinks.h log_sinks.cc

WebRTC提供了FileRotatingLogSink类用于以文件的方式存放日志。FileRotatingLogSink是对FileRotatingStream类的进一步包装。FileRotatingStream类在《WebRTC源码分析之流-Stream》中有介绍。

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

在创建FileRotatingStream对象时,需要提供日志文件所在的目录,日志文件名的前缀,单个文件的大小和文件的数量。

bool FileRotatingLogSink::Init() 
{
  return stream_->Open();
}

创建FileRotatingStream对象后,在往文件中写入数据之前,需要调用Init()函数,调用后文件才会创建。

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

这是子类覆写的虚函数,在LogMessage::~LogMessage()函数的kv.first->OnLogMessage(str, severity_);语句会调用本函数,将日志写入文件中。

CallSessionFileRotatingLogSink类

CallSessionFileRotatingLogSink类所在的文件位置:src\rtc_base\log_sinks.h log_sinks.cc

CallSessionFileRotatingLogSink继承自FileRotatingStream,但将底层的文件流换成了CallSessionFileRotatingStreamCallSessionFileRotatingLogSink生成的日志文件,当写入的数据超过了日志文件的总大小,日志文件将只保留开始的日志和最后的日志。CallSessionFileRotatingLogSink类的更多信息,在《WebRTC源码分析之流-Stream》中有介绍。

小结

本文介绍RTC_LOG源码时是以《WebRTC源码分析之断言-RTC_DCHECK》为前提的。本文介绍了WebRTC中如何使用日志函数RTC_LOG,以及其底层实现。

你可能感兴趣的:(WebRTC源码分析,webrtc,c++)