blob: f0461f79e9642e3745163a5c27424727c4df76e0 [file] [log] [blame]
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "rtc_base/logging.h"
#include <string.h>
#if RTC_LOG_ENABLED()
#if defined(WEBRTC_WIN)
#include <windows.h>
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#undef ERROR // wingdi.h
#endif
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
#include <CoreServices/CoreServices.h>
#elif defined(WEBRTC_ANDROID)
#include <android/log.h>
// Android has a 1024 limit on log inputs. We use 60 chars as an
// approx for the header/tag portion.
// See android/system/core/liblog/logd_write.c
static const int kMaxLogLineSize = 1024 - 60;
#endif // WEBRTC_MAC && !defined(WEBRTC_IOS) || WEBRTC_ANDROID
#include <inttypes.h>
#include <stdio.h>
#include <time.h>
#include <algorithm>
#include <cstdarg>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
#include "api/units/timestamp.h"
#include "rtc_base/checks.h"
#include "rtc_base/platform_thread_types.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/string_utils.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread_annotations.h"
#include "rtc_base/time_utils.h"
namespace rtc {
namespace {
// By default, release builds don't log, debug builds at info level
#if !defined(NDEBUG)
constexpr LoggingSeverity kDefaultLoggingSeverity = LS_INFO;
#else
constexpr LoggingSeverity kDefaultLoggingSeverity = LS_NONE;
#endif
// Note: `g_min_sev` and `g_dbg_sev` can be changed while running.
LoggingSeverity g_min_sev = kDefaultLoggingSeverity;
LoggingSeverity g_dbg_sev = kDefaultLoggingSeverity;
// 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;
}
// Global lock for log subsystem, only needed to serialize access to streams_.
webrtc::Mutex& GetLoggingLock() {
static webrtc::Mutex& mutex = *new webrtc::Mutex();
return mutex;
}
} // namespace
std::string LogLineRef::DefaultLogLine() const {
rtc::StringBuilder log_output;
if (timestamp_ != webrtc::Timestamp::MinusInfinity()) {
// TODO(kwiberg): Switch to absl::StrFormat, if binary size is ok.
char timestamp[50]; // Maximum string length of an int64_t is 20.
int len =
snprintf(timestamp, sizeof(timestamp), "[%03" PRId64 ":%03" PRId64 "]",
timestamp_.ms() / 1000, timestamp_.ms() % 1000);
RTC_DCHECK_LT(len, sizeof(timestamp));
log_output << timestamp;
}
if (thread_id_.has_value()) {
log_output << "[" << *thread_id_ << "] ";
}
if (!filename_.empty()) {
#if defined(WEBRTC_ANDROID)
log_output << "(line " << line_ << "): ";
#else
log_output << "(" << filename_ << ":" << line_ << "): ";
#endif
}
log_output << message_;
return log_output.Release();
}
/////////////////////////////////////////////////////////////////////////////
// LogMessage
/////////////////////////////////////////////////////////////////////////////
bool LogMessage::log_to_stderr_ = true;
// The list of logging streams currently configured.
// Note: we explicitly do not clean this up, because of the uncertain ordering
// of destructors at program exit. Let the person who sets the stream trigger
// cleanup by setting to null, or let it leak (safe at program exit).
ABSL_CONST_INIT LogSink* LogMessage::streams_ RTC_GUARDED_BY(GetLoggingLock()) =
nullptr;
ABSL_CONST_INIT std::atomic<bool> LogMessage::streams_empty_ = {true};
// Boolean options default to false.
ABSL_CONST_INIT bool LogMessage::log_thread_ = false;
ABSL_CONST_INIT bool LogMessage::log_timestamp_ = false;
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) {
log_line_.set_severity(sev);
if (log_timestamp_) {
int64_t log_start_time = LogStartTime();
// 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(), log_start_time);
// Also ensure WallClockStartTime is initialized, so that it matches
// LogStartTime.
WallClockStartTime();
log_line_.set_timestamp(webrtc::Timestamp::Millis(time));
}
if (log_thread_) {
log_line_.set_thread_id(CurrentThreadId());
}
if (file != nullptr) {
log_line_.set_filename(FilenameFromPath(file));
log_line_.set_line(line);
#if defined(WEBRTC_ANDROID)
log_line_.set_tag(log_line_.filename());
#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
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, /*err=*/0) {
log_line_.set_tag(tag);
print_stream_ << tag << ": ";
}
#endif
LogMessage::~LogMessage() {
FinishPrintStream();
log_line_.set_message(print_stream_.Release());
if (log_line_.severity() >= g_dbg_sev) {
OutputToDebug(log_line_);
}
webrtc::MutexLock lock(&GetLoggingLock());
for (LogSink* entry = streams_; entry != nullptr; entry = entry->next_) {
if (log_line_.severity() >= entry->min_severity_) {
entry->OnLogMessage(log_line_);
}
}
}
void LogMessage::AddTag([[maybe_unused]] const char* tag) {
#ifdef WEBRTC_ANDROID
log_line_.set_tag(tag);
#endif
}
rtc::StringBuilder& LogMessage::stream() {
return print_stream_;
}
int LogMessage::GetMinLogSeverity() {
return g_min_sev;
}
LoggingSeverity LogMessage::GetLogToDebug() {
return g_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;
}
void LogMessage::LogThreads(bool on) {
log_thread_ = on;
}
void LogMessage::LogTimestamps(bool on) {
log_timestamp_ = on;
}
void LogMessage::LogToDebug(LoggingSeverity min_sev) {
g_dbg_sev = min_sev;
webrtc::MutexLock lock(&GetLoggingLock());
UpdateMinLogSeverity();
}
void LogMessage::SetLogToStderr(bool log_to_stderr) {
log_to_stderr_ = log_to_stderr;
}
int LogMessage::GetLogToStream(LogSink* stream) {
webrtc::MutexLock lock(&GetLoggingLock());
LoggingSeverity sev = LS_NONE;
for (LogSink* entry = streams_; entry != nullptr; entry = entry->next_) {
if (stream == nullptr || stream == entry) {
sev = std::min(sev, entry->min_severity_);
}
}
return sev;
}
void LogMessage::AddLogToStream(LogSink* stream, LoggingSeverity min_sev) {
webrtc::MutexLock lock(&GetLoggingLock());
stream->min_severity_ = min_sev;
stream->next_ = streams_;
streams_ = stream;
streams_empty_.store(false, std::memory_order_relaxed);
UpdateMinLogSeverity();
}
void LogMessage::RemoveLogToStream(LogSink* stream) {
webrtc::MutexLock lock(&GetLoggingLock());
for (LogSink** entry = &streams_; *entry != nullptr;
entry = &(*entry)->next_) {
if (*entry == stream) {
*entry = (*entry)->next_;
break;
}
}
streams_empty_.store(streams_ == nullptr, std::memory_order_relaxed);
UpdateMinLogSeverity();
}
void LogMessage::ConfigureLogging(absl::string_view 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);
}
void LogMessage::UpdateMinLogSeverity()
RTC_EXCLUSIVE_LOCKS_REQUIRED(GetLoggingLock()) {
LoggingSeverity min_sev = g_dbg_sev;
for (LogSink* entry = streams_; entry != nullptr; entry = entry->next_) {
min_sev = std::min(min_sev, entry->min_severity_);
}
g_min_sev = min_sev;
}
void LogMessage::OutputToDebug(const LogLineRef& log_line) {
std::string msg_str = log_line.DefaultLogLine();
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 domain = CFBundleGetIdentifier(CFBundleGetMainBundle());
if (domain != nullptr) {
Boolean exists_and_is_valid;
Boolean should_log = CFPreferencesGetAppBooleanValue(
CFSTR("logToStdErr"), 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;
}
#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(msg_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, msg_str.c_str(),
static_cast<DWORD>(msg_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 (log_line.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 = msg_str.size();
int current_line = 0;
int idx = 0;
const int max_lines = size / kMaxLogLineSize + 1;
if (max_lines == 1) {
__android_log_print(prio, log_line.tag().data(), "%.*s", size,
msg_str.c_str());
} else {
while (size > 0) {
const int len = std::min(size, kMaxLogLineSize);
// Use the size of the string in the format (msg may have \0 in the
// middle).
__android_log_print(prio, log_line.tag().data(), "[%d/%d] %.*s",
current_line + 1, max_lines, len,
msg_str.c_str() + idx);
idx += len;
size -= len;
++current_line;
}
}
#endif // WEBRTC_ANDROID
if (log_to_stderr) {
fprintf(stderr, "%s", msg_str.c_str());
fflush(stderr);
}
}
// static
bool LogMessage::IsNoop(LoggingSeverity severity) {
if (severity >= g_dbg_sev || severity >= g_min_sev)
return false;
return streams_empty_.load(std::memory_order_relaxed);
}
void LogMessage::FinishPrintStream() {
if (!extra_.empty())
print_stream_ << " : " << extra_;
print_stream_ << "\n";
}
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_DCHECK_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: {
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_DCHECK_NOTREACHED();
va_end(args);
return;
}
}
va_end(args);
}
} // namespace webrtc_logging_impl
} // namespace rtc
#endif
namespace rtc {
// Default implementation, override is recomended.
void LogSink::OnLogMessage(const LogLineRef& log_line) {
#if defined(WEBRTC_ANDROID)
OnLogMessage(log_line.DefaultLogLine(), log_line.severity(),
log_line.tag().data());
#else
OnLogMessage(log_line.DefaultLogLine(), log_line.severity());
#endif
}
// 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);
}
// Inefficient default implementation, override is recommended.
void LogSink::OnLogMessage(absl::string_view msg,
LoggingSeverity severity,
const char* tag) {
OnLogMessage(tag + (": " + std::string(msg)), severity);
}
void LogSink::OnLogMessage(absl::string_view msg,
LoggingSeverity /* severity */) {
OnLogMessage(msg);
}
void LogSink::OnLogMessage(absl::string_view msg) {
OnLogMessage(std::string(msg));
}
} // namespace rtc