blob: 2b2c756e37655ee19417a8f72d8c70187878445b [file] [log] [blame] [edit]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "log.h"
#include <fidl/fuchsia.logger/cpp/fidl.h>
#include <fidl/fuchsia.logger/cpp/wire_types.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <zircon/process.h>
#include <fbl/no_destructor.h>
#include "src/lib/stdformat/print.h"
namespace fdf_log {
namespace {
const char* StripPath(const char* path) {
auto p = strrchr(path, '/');
if (p) {
return p + 1;
}
return path;
}
const char* StripDots(const char* path) {
while (strncmp(path, "../", 3) == 0) {
path += 3;
}
return path;
}
const char* StripFile(const char* file, FuchsiaLogSeverity severity) {
return severity > FUCHSIA_LOG_INFO ? StripDots(file) : StripPath(file);
}
zx_koid_t GetKoid(zx_handle_t handle) {
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
}
#if FUCHSIA_API_LEVEL_LESS_THAN(29)
Logger& GetOrCreateLogger() {
static fbl::NoDestructor<Logger> logger = [] {
zx::channel logger_request, logger_client;
zx::channel::create(0, &logger_request, &logger_client);
fdio_service_connect("/svc/fuchsia.logger.LogSink", logger_request.release());
zx::socket remote, local;
zx::socket::create(ZX_SOCKET_DATAGRAM, &remote, &local);
fidl::ClientEnd<fuchsia_logger::LogSink> log_sink{std::move(logger_client)};
auto status = fidl::WireCall(log_sink)->ConnectStructured(std::move(remote));
if (!status.ok()) {
ZX_PANIC("Failed to create logger: %s", status.status_string());
}
return Logger(GetKoid(zx_process_self()), std::move(local));
}();
return *logger;
}
#else
Logger CreateLogger(fuchsia_logging::RawLogSettings* settings) {
zx_koid_t pid = GetKoid(zx_process_self());
auto client_end = component::Connect<fuchsia_logger::LogSink>();
if (client_end.is_ok()) {
std::optional<fuchsia_logging::RawLogSettings> settings_storage;
if (!settings) {
settings = &settings_storage.emplace();
}
settings->log_sink = client_end->TakeChannel().release();
auto logger = fuchsia_logging::Logger::Create(*settings);
if (logger.is_ok()) {
return Logger(pid, *std::move(logger));
}
}
return Logger(pid, {});
}
Logger& GetOrCreateLogger(fuchsia_logging::RawLogSettings* settings = nullptr) {
bool called = false;
static fbl::NoDestructor<Logger> logger = [called, settings]() mutable {
called = true;
return CreateLogger(settings);
}();
if (!called && settings) {
*logger = CreateLogger(settings);
}
return *logger;
}
#endif
zx_koid_t GetCurrentThreadKoid() { return GetKoid(zx_thread_self()); }
thread_local zx_koid_t tid = GetCurrentThreadKoid();
} // namespace
namespace internal {
FuchsiaLogSeverity severity_from_verbosity(uint8_t verbosity) {
// verbosity scale sits in the interstitial space between INFO and DEBUG
FuchsiaLogSeverity severity = FUCHSIA_LOG_INFO - (verbosity * FUCHSIA_LOG_VERBOSITY_STEP_SIZE);
if (severity < FUCHSIA_LOG_DEBUG + 1) {
return FUCHSIA_LOG_DEBUG + 1;
}
return severity;
}
void log_with_source(Logger& logger, FuchsiaLogSeverity severity, const char* tag, const char* file,
int line, const char* format, ...) {
va_list args;
va_start(args, format);
logger.VLogWrite(severity, tag, format, args, file, line);
va_end(args);
}
const char* SeverityToString(FuchsiaLogSeverity severity) {
switch (severity) {
case FUCHSIA_LOG_TRACE:
return "TRACE";
case FUCHSIA_LOG_DEBUG:
return "DEBUG";
case FUCHSIA_LOG_INFO:
return "INFO";
case FUCHSIA_LOG_WARNING:
return "WARN";
case FUCHSIA_LOG_ERROR:
return "ERROR";
case FUCHSIA_LOG_FATAL:
return "FATAL";
default:
return "";
}
}
} // namespace internal
void Logger::VLogWrite(FuchsiaLogSeverity severity, const char* tag, const char* msg, va_list args,
const char* file, uint32_t line) const {
if (severity < GetSeverity()) {
return;
}
if (use_stdout_) {
// We rely on line buffering to ensure this is a single syscall.
printf("[driver_manager.cm]: %s: ", internal::SeverityToString(severity));
vprintf(msg, args);
fputc('\n', stdout);
return;
}
fuchsia_logging::LogBuffer buffer;
constexpr size_t kFormatStringLength = 1024;
char fmt_string[kFormatStringLength];
fmt_string[kFormatStringLength - 1] = 0;
int n = kFormatStringLength;
// Format
// Number of bytes written not including null terminator
int count = 0;
count = vsnprintf(fmt_string, n, msg, args) + 1;
if (count >= n) {
// truncated
constexpr char kEllipsis[] = "...";
constexpr size_t kEllipsisSize = sizeof(kEllipsis);
snprintf(fmt_string + kFormatStringLength - 1 - kEllipsisSize, kEllipsisSize, kEllipsis);
}
if (file) {
file = StripFile(file, severity);
}
BeginRecord(buffer, severity, file, line, fmt_string);
#if FUCHSIA_API_LEVEL_LESS_THAN(29)
for (const auto& global_tag : global_tags_) {
buffer.WriteKeyValue("tag", global_tag);
}
#endif
if (tag) {
buffer.WriteKeyValue("tag", tag);
}
#if FUCHSIA_API_LEVEL_LESS_THAN(29)
FlushRecord(buffer, severity);
#else
[[maybe_unused]] zx::result<> result = logger_.FlushBuffer(buffer);
#endif
}
void Logger::BeginRecord(fuchsia_logging::LogBuffer& buffer, FuchsiaLogSeverity severity,
std::optional<std::string_view> file_name, unsigned int line,
std::optional<std::string_view> msg) const {
buffer.BeginRecord(severity, file_name, line, msg,
#if FUCHSIA_API_LEVEL_LESS_THAN(29)
socket_.borrow(),
#endif
0, pid_, GetCurrentThread());
}
#if FUCHSIA_API_LEVEL_LESS_THAN(29)
void Logger::FlushRecord(fuchsia_logging::LogBuffer& buffer, FuchsiaLogSeverity severity) const {
buffer.FlushRecord();
}
#endif
zx_koid_t GetCurrentThread() { return tid; }
Logger& GetLogger() { return GetOrCreateLogger(); }
#if FUCHSIA_API_LEVEL_AT_LEAST(PLATFORM)
void InitGlobalLogger(std::span<const char*> tags, FuchsiaLogSeverity severity) {
fuchsia_logging::RawLogSettings settings{
.min_log_level = severity,
.tags = tags.data(),
.tags_count = tags.size(),
};
GetOrCreateLogger(&settings);
}
#endif
#if FUCHSIA_API_LEVEL_AT_LEAST(HEAD) && __cplusplus >= 202002L
namespace {
template <typename T, std::size_t N>
class array_output_iterator {
public:
using iterator_category = std::output_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
explicit array_output_iterator(std::array<T, N>& arr, size_t& actual_size)
: arr_(arr), actual_size_(actual_size) {}
array_output_iterator(array_output_iterator&& other)
: arr_(other.arr_), actual_size_(other.actual_size_), index_(other.index_) {}
array_output_iterator& operator=(array_output_iterator&& other) {
arr_ = other.arr_;
actual_size_ = other.actual_size_;
index_ = other.index_;
return *this;
}
array_output_iterator& operator=(const T& value) {
if (index_ < N) {
arr_[index_] = value;
}
return *this;
}
reference operator*() { return arr_[index_]; }
array_output_iterator& operator++() {
++index_;
actual_size_++;
return *this;
}
array_output_iterator operator++(int) {
auto tmp = *this;
++index_;
actual_size_++;
return tmp;
}
private:
std::array<T, N>& arr_;
size_t& actual_size_;
size_t index_ = 0;
};
template <typename T, size_t N>
array_output_iterator(std::array<T, N>&, size_t&) -> array_output_iterator<T, N>;
} // namespace
void Logger::LogInternal(FuchsiaLogSeverity severity, const char* tag, const char* file, int line,
std::string_view fmt, std::format_args args) {
// The complementary cases are handled directly in the header, to rely on
// std::format_string template pack.
ZX_ASSERT(severity >= GetSeverity());
ZX_ASSERT(!use_stdout_);
constexpr size_t kFormatStringLength = 1024;
std::array<char, kFormatStringLength> fmt_buffer;
size_t actual_size = 0;
std::vformat_to(array_output_iterator(fmt_buffer, actual_size), fmt, args);
if (actual_size == 0) {
return;
}
if (actual_size >= kFormatStringLength) {
// truncated
constexpr char kEllipsis[] = "...";
constexpr size_t kEllipsisSize = sizeof(kEllipsis);
snprintf(fmt_buffer.data() + kFormatStringLength - kEllipsisSize, kEllipsisSize, kEllipsis);
}
fmt_buffer[kFormatStringLength - 1] = 0;
file = StripFile(file, severity);
fuchsia_logging::LogBuffer buffer;
BeginRecord(buffer, severity, file, line,
std::string_view(fmt_buffer.data(), std::min(actual_size, kFormatStringLength)));
if (tag) {
buffer.WriteKeyValue("tag", tag);
}
[[maybe_unused]] zx::result<> result = logger_.FlushBuffer(buffer);
if (severity == FUCHSIA_LOG_FATAL) {
abort();
}
}
#endif
} // namespace fdf_log