Previously, we would split messages by line and call the logger function for each line. We would hold a lock during this, to ensure that multiple threads would not interleave their messages. There are a few problems with this approach: 1) Using a lock is not efficient and is not fork safe 2) With APEX, there is one lock per instance of libbase, so we must move the lock to a location where all instances can access it, or perform the line splitting in a way that does not require the lock. To solve these issues, we reimagine line splitting. 1) We move the lock out of the LogMessage::~LogMessage() and make it the logger's responsibility to split lines, giving the logger the option to lock or not. 2) We do not need any locks at all for StderrLogger. Instead, we generate a single string that contains all of the lines with their appropriate log header. A single write() call is used to output this at once. 3) Logd handles log messages with newlines correctly, however it only accepts up to a maximum size of log message. Therefore we separate the incoming log message into chunks, delimited by new lines, up to that maximum size, and send each of those to logd. Note that this is the strategy used in android.util.Log.printlns(). This should solve a majority of use cases, since the maximum size that logd accepts is nearly 4K, while remaining lock free. If interleaving messages absolutely must be avoided, a lock can still be used given 1) above. Bug: 65062446 Bug: 153824050 Test: logging, particularly multi-line stack traces, show correctly Test: existing and new unit tests Change-Id: Id0cb5669bee7f912da1e17f7010f0ee4c93be1e3
585 lines
16 KiB
C++
585 lines
16 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#if defined(_WIN32)
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include "android-base/logging.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <libgen.h>
|
|
#include <time.h>
|
|
|
|
// For getprogname(3) or program_invocation_short_name.
|
|
#if defined(__ANDROID__) || defined(__APPLE__)
|
|
#include <stdlib.h>
|
|
#elif defined(__GLIBC__)
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
#if defined(__linux__)
|
|
#include <sys/uio.h>
|
|
#endif
|
|
|
|
#include <atomic>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <android/log.h>
|
|
#ifdef __ANDROID__
|
|
#include <android/set_abort_message.h>
|
|
#else
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/macros.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/strings.h>
|
|
#include <android-base/threads.h>
|
|
|
|
#include "liblog_symbols.h"
|
|
#include "logging_splitters.h"
|
|
|
|
namespace android {
|
|
namespace base {
|
|
|
|
// BSD-based systems like Android/macOS have getprogname(). Others need us to provide one.
|
|
#if defined(__GLIBC__) || defined(_WIN32)
|
|
static const char* getprogname() {
|
|
#if defined(__GLIBC__)
|
|
return program_invocation_short_name;
|
|
#elif defined(_WIN32)
|
|
static bool first = true;
|
|
static char progname[MAX_PATH] = {};
|
|
|
|
if (first) {
|
|
snprintf(progname, sizeof(progname), "%s",
|
|
android::base::Basename(android::base::GetExecutablePath()).c_str());
|
|
first = false;
|
|
}
|
|
|
|
return progname;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
static const char* GetFileBasename(const char* file) {
|
|
// We can't use basename(3) even on Unix because the Mac doesn't
|
|
// have a non-modifying basename.
|
|
const char* last_slash = strrchr(file, '/');
|
|
if (last_slash != nullptr) {
|
|
return last_slash + 1;
|
|
}
|
|
#if defined(_WIN32)
|
|
const char* last_backslash = strrchr(file, '\\');
|
|
if (last_backslash != nullptr) {
|
|
return last_backslash + 1;
|
|
}
|
|
#endif
|
|
return file;
|
|
}
|
|
|
|
#if defined(__linux__)
|
|
static int OpenKmsg() {
|
|
#if defined(__ANDROID__)
|
|
// pick up 'file w /dev/kmsg' environment from daemon's init rc file
|
|
const auto val = getenv("ANDROID_FILE__dev_kmsg");
|
|
if (val != nullptr) {
|
|
int fd;
|
|
if (android::base::ParseInt(val, &fd, 0)) {
|
|
auto flags = fcntl(fd, F_GETFL);
|
|
if ((flags != -1) && ((flags & O_ACCMODE) == O_WRONLY)) return fd;
|
|
}
|
|
}
|
|
#endif
|
|
return TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
|
|
}
|
|
#endif
|
|
|
|
static LogId log_id_tToLogId(int32_t buffer_id) {
|
|
switch (buffer_id) {
|
|
case LOG_ID_MAIN:
|
|
return MAIN;
|
|
case LOG_ID_SYSTEM:
|
|
return SYSTEM;
|
|
case LOG_ID_RADIO:
|
|
return RADIO;
|
|
case LOG_ID_CRASH:
|
|
return CRASH;
|
|
case LOG_ID_DEFAULT:
|
|
default:
|
|
return DEFAULT;
|
|
}
|
|
}
|
|
|
|
static int32_t LogIdTolog_id_t(LogId log_id) {
|
|
switch (log_id) {
|
|
case MAIN:
|
|
return LOG_ID_MAIN;
|
|
case SYSTEM:
|
|
return LOG_ID_SYSTEM;
|
|
case RADIO:
|
|
return LOG_ID_RADIO;
|
|
case CRASH:
|
|
return LOG_ID_CRASH;
|
|
case DEFAULT:
|
|
default:
|
|
return LOG_ID_DEFAULT;
|
|
}
|
|
}
|
|
|
|
static LogSeverity PriorityToLogSeverity(int priority) {
|
|
switch (priority) {
|
|
case ANDROID_LOG_DEFAULT:
|
|
return INFO;
|
|
case ANDROID_LOG_VERBOSE:
|
|
return VERBOSE;
|
|
case ANDROID_LOG_DEBUG:
|
|
return DEBUG;
|
|
case ANDROID_LOG_INFO:
|
|
return INFO;
|
|
case ANDROID_LOG_WARN:
|
|
return WARNING;
|
|
case ANDROID_LOG_ERROR:
|
|
return ERROR;
|
|
case ANDROID_LOG_FATAL:
|
|
return FATAL;
|
|
default:
|
|
return FATAL;
|
|
}
|
|
}
|
|
|
|
static int32_t LogSeverityToPriority(LogSeverity severity) {
|
|
switch (severity) {
|
|
case VERBOSE:
|
|
return ANDROID_LOG_VERBOSE;
|
|
case DEBUG:
|
|
return ANDROID_LOG_DEBUG;
|
|
case INFO:
|
|
return ANDROID_LOG_INFO;
|
|
case WARNING:
|
|
return ANDROID_LOG_WARN;
|
|
case ERROR:
|
|
return ANDROID_LOG_ERROR;
|
|
case FATAL_WITHOUT_ABORT:
|
|
case FATAL:
|
|
default:
|
|
return ANDROID_LOG_FATAL;
|
|
}
|
|
}
|
|
|
|
static LogFunction& Logger() {
|
|
#ifdef __ANDROID__
|
|
static auto& logger = *new LogFunction(LogdLogger());
|
|
#else
|
|
static auto& logger = *new LogFunction(StderrLogger);
|
|
#endif
|
|
return logger;
|
|
}
|
|
|
|
static AbortFunction& Aborter() {
|
|
static auto& aborter = *new AbortFunction(DefaultAborter);
|
|
return aborter;
|
|
}
|
|
|
|
// Only used for Q fallback.
|
|
static std::recursive_mutex& TagLock() {
|
|
static auto& tag_lock = *new std::recursive_mutex();
|
|
return tag_lock;
|
|
}
|
|
// Only used for Q fallback.
|
|
static std::string* gDefaultTag;
|
|
|
|
void SetDefaultTag(const std::string& tag) {
|
|
static auto& liblog_functions = GetLibLogFunctions();
|
|
if (liblog_functions) {
|
|
liblog_functions->__android_log_set_default_tag(tag.c_str());
|
|
} else {
|
|
std::lock_guard<std::recursive_mutex> lock(TagLock());
|
|
if (gDefaultTag != nullptr) {
|
|
delete gDefaultTag;
|
|
gDefaultTag = nullptr;
|
|
}
|
|
if (!tag.empty()) {
|
|
gDefaultTag = new std::string(tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool gInitialized = false;
|
|
|
|
// Only used for Q fallback.
|
|
static LogSeverity gMinimumLogSeverity = INFO;
|
|
|
|
#if defined(__linux__)
|
|
static void KernelLogLine(const char* msg, int length, android::base::LogSeverity severity,
|
|
const char* tag) {
|
|
// clang-format off
|
|
static constexpr int kLogSeverityToKernelLogLevel[] = {
|
|
[android::base::VERBOSE] = 7, // KERN_DEBUG (there is no verbose kernel log
|
|
// level)
|
|
[android::base::DEBUG] = 7, // KERN_DEBUG
|
|
[android::base::INFO] = 6, // KERN_INFO
|
|
[android::base::WARNING] = 4, // KERN_WARNING
|
|
[android::base::ERROR] = 3, // KERN_ERROR
|
|
[android::base::FATAL_WITHOUT_ABORT] = 2, // KERN_CRIT
|
|
[android::base::FATAL] = 2, // KERN_CRIT
|
|
};
|
|
// clang-format on
|
|
static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1,
|
|
"Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");
|
|
|
|
static int klog_fd = OpenKmsg();
|
|
if (klog_fd == -1) return;
|
|
|
|
int level = kLogSeverityToKernelLogLevel[severity];
|
|
|
|
// The kernel's printk buffer is only 1024 bytes.
|
|
// TODO: should we automatically break up long lines into multiple lines?
|
|
// Or we could log but with something like "..." at the end?
|
|
char buf[1024];
|
|
size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %.*s\n", level, tag, length, msg);
|
|
if (size > sizeof(buf)) {
|
|
size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n",
|
|
level, tag, size);
|
|
}
|
|
|
|
iovec iov[1];
|
|
iov[0].iov_base = buf;
|
|
iov[0].iov_len = size;
|
|
TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));
|
|
}
|
|
|
|
void KernelLogger(android::base::LogId, android::base::LogSeverity severity, const char* tag,
|
|
const char*, unsigned int, const char* full_message) {
|
|
SplitByLines(full_message, KernelLogLine, severity, tag);
|
|
}
|
|
#endif
|
|
|
|
void StderrLogger(LogId, LogSeverity severity, const char* tag, const char* file, unsigned int line,
|
|
const char* message) {
|
|
struct tm now;
|
|
time_t t = time(nullptr);
|
|
|
|
#if defined(_WIN32)
|
|
localtime_s(&now, &t);
|
|
#else
|
|
localtime_r(&t, &now);
|
|
#endif
|
|
auto output_string =
|
|
StderrOutputGenerator(now, getpid(), GetThreadId(), severity, tag, file, line, message);
|
|
|
|
fputs(output_string.c_str(), stderr);
|
|
}
|
|
|
|
void StdioLogger(LogId, LogSeverity severity, const char* /*tag*/, const char* /*file*/,
|
|
unsigned int /*line*/, const char* message) {
|
|
if (severity >= WARNING) {
|
|
fflush(stdout);
|
|
fprintf(stderr, "%s: %s\n", GetFileBasename(getprogname()), message);
|
|
} else {
|
|
fprintf(stdout, "%s\n", message);
|
|
}
|
|
}
|
|
|
|
void DefaultAborter(const char* abort_message) {
|
|
#ifdef __ANDROID__
|
|
android_set_abort_message(abort_message);
|
|
#else
|
|
UNUSED(abort_message);
|
|
#endif
|
|
abort();
|
|
}
|
|
|
|
static void LogdLogChunk(LogId id, LogSeverity severity, const char* tag, const char* message) {
|
|
int32_t lg_id = LogIdTolog_id_t(id);
|
|
int32_t priority = LogSeverityToPriority(severity);
|
|
|
|
static auto& liblog_functions = GetLibLogFunctions();
|
|
if (liblog_functions) {
|
|
__android_log_message log_message = {sizeof(__android_log_message), lg_id, priority, tag,
|
|
static_cast<const char*>(nullptr), 0, message};
|
|
liblog_functions->__android_log_logd_logger(&log_message);
|
|
} else {
|
|
__android_log_buf_print(lg_id, priority, tag, "%s", message);
|
|
}
|
|
}
|
|
|
|
LogdLogger::LogdLogger(LogId default_log_id) : default_log_id_(default_log_id) {}
|
|
|
|
void LogdLogger::operator()(LogId id, LogSeverity severity, const char* tag, const char* file,
|
|
unsigned int line, const char* message) {
|
|
if (id == DEFAULT) {
|
|
id = default_log_id_;
|
|
}
|
|
|
|
SplitByLogdChunks(id, severity, tag, file, line, message, LogdLogChunk);
|
|
}
|
|
|
|
void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {
|
|
SetLogger(std::forward<LogFunction>(logger));
|
|
SetAborter(std::forward<AbortFunction>(aborter));
|
|
|
|
if (gInitialized) {
|
|
return;
|
|
}
|
|
|
|
gInitialized = true;
|
|
|
|
// Stash the command line for later use. We can use /proc/self/cmdline on
|
|
// Linux to recover this, but we don't have that luxury on the Mac/Windows,
|
|
// and there are a couple of argv[0] variants that are commonly used.
|
|
if (argv != nullptr) {
|
|
SetDefaultTag(basename(argv[0]));
|
|
}
|
|
|
|
const char* tags = getenv("ANDROID_LOG_TAGS");
|
|
if (tags == nullptr) {
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> specs = Split(tags, " ");
|
|
for (size_t i = 0; i < specs.size(); ++i) {
|
|
// "tag-pattern:[vdiwefs]"
|
|
std::string spec(specs[i]);
|
|
if (spec.size() == 3 && StartsWith(spec, "*:")) {
|
|
switch (spec[2]) {
|
|
case 'v':
|
|
SetMinimumLogSeverity(VERBOSE);
|
|
continue;
|
|
case 'd':
|
|
SetMinimumLogSeverity(DEBUG);
|
|
continue;
|
|
case 'i':
|
|
SetMinimumLogSeverity(INFO);
|
|
continue;
|
|
case 'w':
|
|
SetMinimumLogSeverity(WARNING);
|
|
continue;
|
|
case 'e':
|
|
SetMinimumLogSeverity(ERROR);
|
|
continue;
|
|
case 'f':
|
|
SetMinimumLogSeverity(FATAL_WITHOUT_ABORT);
|
|
continue;
|
|
// liblog will even suppress FATAL if you say 's' for silent, but that's
|
|
// crazy!
|
|
case 's':
|
|
SetMinimumLogSeverity(FATAL_WITHOUT_ABORT);
|
|
continue;
|
|
}
|
|
}
|
|
LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags
|
|
<< ")";
|
|
}
|
|
}
|
|
|
|
void SetLogger(LogFunction&& logger) {
|
|
Logger() = std::move(logger);
|
|
|
|
static auto& liblog_functions = GetLibLogFunctions();
|
|
if (liblog_functions) {
|
|
liblog_functions->__android_log_set_logger([](const struct __android_log_message* log_message) {
|
|
auto log_id = log_id_tToLogId(log_message->buffer_id);
|
|
auto severity = PriorityToLogSeverity(log_message->priority);
|
|
|
|
Logger()(log_id, severity, log_message->tag, log_message->file, log_message->line,
|
|
log_message->message);
|
|
});
|
|
}
|
|
}
|
|
|
|
void SetAborter(AbortFunction&& aborter) {
|
|
Aborter() = std::move(aborter);
|
|
|
|
static auto& liblog_functions = GetLibLogFunctions();
|
|
if (liblog_functions) {
|
|
liblog_functions->__android_log_set_aborter(
|
|
[](const char* abort_message) { Aborter()(abort_message); });
|
|
}
|
|
}
|
|
|
|
// This indirection greatly reduces the stack impact of having lots of
|
|
// checks/logging in a function.
|
|
class LogMessageData {
|
|
public:
|
|
LogMessageData(const char* file, unsigned int line, LogSeverity severity, const char* tag,
|
|
int error)
|
|
: file_(GetFileBasename(file)),
|
|
line_number_(line),
|
|
severity_(severity),
|
|
tag_(tag),
|
|
error_(error) {}
|
|
|
|
const char* GetFile() const {
|
|
return file_;
|
|
}
|
|
|
|
unsigned int GetLineNumber() const {
|
|
return line_number_;
|
|
}
|
|
|
|
LogSeverity GetSeverity() const {
|
|
return severity_;
|
|
}
|
|
|
|
const char* GetTag() const { return tag_; }
|
|
|
|
int GetError() const {
|
|
return error_;
|
|
}
|
|
|
|
std::ostream& GetBuffer() {
|
|
return buffer_;
|
|
}
|
|
|
|
std::string ToString() const {
|
|
return buffer_.str();
|
|
}
|
|
|
|
private:
|
|
std::ostringstream buffer_;
|
|
const char* const file_;
|
|
const unsigned int line_number_;
|
|
const LogSeverity severity_;
|
|
const char* const tag_;
|
|
const int error_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(LogMessageData);
|
|
};
|
|
|
|
LogMessage::LogMessage(const char* file, unsigned int line, LogId, LogSeverity severity,
|
|
const char* tag, int error)
|
|
: LogMessage(file, line, severity, tag, error) {}
|
|
|
|
LogMessage::LogMessage(const char* file, unsigned int line, LogSeverity severity, const char* tag,
|
|
int error)
|
|
: data_(new LogMessageData(file, line, severity, tag, error)) {}
|
|
|
|
LogMessage::~LogMessage() {
|
|
// Check severity again. This is duplicate work wrt/ LOG macros, but not LOG_STREAM.
|
|
if (!WOULD_LOG(data_->GetSeverity())) {
|
|
return;
|
|
}
|
|
|
|
// Finish constructing the message.
|
|
if (data_->GetError() != -1) {
|
|
data_->GetBuffer() << ": " << strerror(data_->GetError());
|
|
}
|
|
std::string msg(data_->ToString());
|
|
|
|
if (data_->GetSeverity() == FATAL) {
|
|
#ifdef __ANDROID__
|
|
// Set the bionic abort message early to avoid liblog doing it
|
|
// with the individual lines, so that we get the whole message.
|
|
android_set_abort_message(msg.c_str());
|
|
#endif
|
|
}
|
|
|
|
LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(), data_->GetTag(),
|
|
msg.c_str());
|
|
|
|
// Abort if necessary.
|
|
if (data_->GetSeverity() == FATAL) {
|
|
static auto& liblog_functions = GetLibLogFunctions();
|
|
if (liblog_functions) {
|
|
liblog_functions->__android_log_call_aborter(msg.c_str());
|
|
} else {
|
|
Aborter()(msg.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::ostream& LogMessage::stream() {
|
|
return data_->GetBuffer();
|
|
}
|
|
|
|
void LogMessage::LogLine(const char* file, unsigned int line, LogSeverity severity, const char* tag,
|
|
const char* message) {
|
|
static auto& liblog_functions = GetLibLogFunctions();
|
|
int32_t priority = LogSeverityToPriority(severity);
|
|
if (liblog_functions) {
|
|
__android_log_message log_message = {
|
|
sizeof(__android_log_message), LOG_ID_DEFAULT, priority, tag, file, line, message};
|
|
liblog_functions->__android_log_write_log_message(&log_message);
|
|
} else {
|
|
if (tag == nullptr) {
|
|
std::lock_guard<std::recursive_mutex> lock(TagLock());
|
|
if (gDefaultTag == nullptr) {
|
|
gDefaultTag = new std::string(getprogname());
|
|
}
|
|
|
|
Logger()(DEFAULT, severity, gDefaultTag->c_str(), file, line, message);
|
|
} else {
|
|
Logger()(DEFAULT, severity, tag, file, line, message);
|
|
}
|
|
}
|
|
}
|
|
|
|
LogSeverity GetMinimumLogSeverity() {
|
|
static auto& liblog_functions = GetLibLogFunctions();
|
|
if (liblog_functions) {
|
|
return PriorityToLogSeverity(liblog_functions->__android_log_get_minimum_priority());
|
|
} else {
|
|
return gMinimumLogSeverity;
|
|
}
|
|
}
|
|
|
|
bool ShouldLog(LogSeverity severity, const char* tag) {
|
|
static auto& liblog_functions = GetLibLogFunctions();
|
|
// Even though we're not using the R liblog functions in this function, if we're running on Q,
|
|
// we need to fall back to using gMinimumLogSeverity, since __android_log_is_loggable() will not
|
|
// take into consideration the value from SetMinimumLogSeverity().
|
|
if (liblog_functions) {
|
|
int32_t priority = LogSeverityToPriority(severity);
|
|
return __android_log_is_loggable(priority, tag, ANDROID_LOG_INFO);
|
|
} else {
|
|
return severity >= gMinimumLogSeverity;
|
|
}
|
|
}
|
|
|
|
LogSeverity SetMinimumLogSeverity(LogSeverity new_severity) {
|
|
static auto& liblog_functions = GetLibLogFunctions();
|
|
if (liblog_functions) {
|
|
int32_t priority = LogSeverityToPriority(new_severity);
|
|
return PriorityToLogSeverity(liblog_functions->__android_log_set_minimum_priority(priority));
|
|
} else {
|
|
LogSeverity old_severity = gMinimumLogSeverity;
|
|
gMinimumLogSeverity = new_severity;
|
|
return old_severity;
|
|
}
|
|
}
|
|
|
|
ScopedLogSeverity::ScopedLogSeverity(LogSeverity new_severity) {
|
|
old_ = SetMinimumLogSeverity(new_severity);
|
|
}
|
|
|
|
ScopedLogSeverity::~ScopedLogSeverity() {
|
|
SetMinimumLogSeverity(old_);
|
|
}
|
|
|
|
} // namespace base
|
|
} // namespace android
|