| // Copyright 2020 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 <fidl/fuchsia.logger/cpp/wire_messaging.h> |
| #include <lib/driver/logging/cpp/logger.h> |
| #include <lib/fdio/directory.h> |
| #include <zircon/process.h> |
| |
| #include <cstdarg> |
| |
| namespace flog = ::fuchsia_logging; |
| |
| namespace fdf { |
| |
| namespace { |
| std::atomic<Logger*> g_instance = nullptr; |
| |
| #if FUCHSIA_API_LEVEL_AT_LEAST(27) |
| using FidlSeverity = fuchsia_diagnostics_types::wire::Severity; |
| using FidlInterest = fuchsia_diagnostics_types::wire::Interest; |
| #else |
| using FidlSeverity = fuchsia_diagnostics::wire::Severity; |
| using FidlInterest = fuchsia_diagnostics::wire::Interest; |
| #endif |
| |
| } // namespace |
| |
| 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; |
| } |
| |
| bool Logger::FlushRecord(flog::LogBuffer& buffer, uint32_t dropped) { |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| if (!buffer.FlushRecord()) { |
| dropped_logs_.fetch_add(dropped + 1, std::memory_order_relaxed); |
| return false; |
| } |
| #else |
| if (logger_.FlushBuffer(buffer).is_error()) { |
| dropped_logs_.fetch_add(dropped + 1, std::memory_order_relaxed); |
| return false; |
| } |
| #endif |
| return true; |
| } |
| |
| void Logger::BeginRecord(flog::LogBuffer& buffer, FuchsiaLogSeverity severity, |
| std::optional<std::string_view> file_name, unsigned int line, |
| std::optional<std::string_view> message, uint32_t dropped) { |
| static zx_koid_t pid = GetKoid(zx_process_self()); |
| static thread_local zx_koid_t tid = GetKoid(zx_thread_self()); |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| buffer.BeginRecord(severity, file_name, line, message, socket_.borrow(), dropped, pid, tid); |
| buffer.WriteKeyValue("tag", "driver"); |
| buffer.WriteKeyValue("tag", tag_); |
| #else |
| buffer.BeginRecord(severity, file_name, line, message, dropped, pid, tid); |
| #endif |
| } |
| |
| std::unique_ptr<Logger> Logger::NoOp() { return std::make_unique<Logger>(); } |
| |
| std::unique_ptr<Logger> Logger::Create2(const Namespace& ns, async_dispatcher_t* dispatcher, |
| std::string_view name, FuchsiaLogSeverity min_severity |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| , |
| bool wait_for_initial_interest |
| #endif |
| ) { |
| auto result = Logger::MaybeCreate(ns, dispatcher, name, min_severity |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| , |
| wait_for_initial_interest |
| #endif |
| ); |
| if (!result.is_ok()) { |
| return Logger::NoOp(); |
| } |
| return std::move(result.value()); |
| } |
| |
| zx::result<std::unique_ptr<Logger>> Logger::Create(const Namespace& ns, |
| async_dispatcher_t* dispatcher, |
| std::string_view name, |
| FuchsiaLogSeverity min_severity |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| , |
| bool wait_for_initial_interest |
| #endif |
| ) { |
| auto result = Logger::MaybeCreate(ns, dispatcher, name, min_severity |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| , |
| wait_for_initial_interest |
| #endif |
| ); |
| if (!result.is_ok()) { |
| return zx::ok(Logger::NoOp()); |
| } |
| return result; |
| } |
| |
| zx::result<std::unique_ptr<Logger>> Logger::MaybeCreate(const Namespace& ns, |
| async_dispatcher_t* dispatcher, |
| std::string_view name, |
| FuchsiaLogSeverity min_severity |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| , |
| bool wait_for_initial_interest |
| #endif |
| ) { |
| auto ns_result = ns.Connect<fuchsia_logger::LogSink>(); |
| if (ns_result.is_error()) { |
| return ns_result.take_error(); |
| } |
| |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| zx::socket client_end, server_end; |
| zx_status_t status = zx::socket::create(ZX_SOCKET_DATAGRAM, &client_end, &server_end); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| fidl::WireClient<fuchsia_logger::LogSink> log_sink(std::move(*ns_result), dispatcher); |
| auto sink_result = log_sink->ConnectStructured(std::move(server_end)); |
| if (!sink_result.ok()) { |
| return zx::error(sink_result.status()); |
| } |
| |
| auto logger = |
| std::make_unique<Logger>(name, min_severity, std::move(client_end), std::move(log_sink)); |
| |
| if (wait_for_initial_interest) { |
| auto interest_result = logger->log_sink_.sync()->WaitForInterestChange(); |
| if (!interest_result.ok()) { |
| return zx::error(interest_result.status()); |
| } |
| // We are guanteed to not call this twice so we can ignore the application error. |
| logger->HandleInterest(interest_result->value()->data); |
| } |
| |
| logger->log_sink_->WaitForInterestChange().Then( |
| fit::bind_member(logger.get(), &Logger::OnInterestChange)); |
| |
| return zx::ok(std::move(logger)); |
| #else |
| std::string name_str(name); |
| const char* tags[] = {"driver", name_str.c_str()}; |
| if (auto logger = fuchsia_logging::Logger::Create(fuchsia_logging::RawLogSettings{ |
| .min_log_level = min_severity, |
| .log_sink = ns_result->TakeChannel().release(), |
| .tags = tags, |
| .tags_count = 2, |
| .dispatcher = dispatcher, |
| }); |
| logger.is_error()) { |
| return logger.take_error(); |
| } else { |
| return zx::ok(std::make_unique<Logger>(*std::move(logger))); |
| } |
| #endif |
| } |
| |
| Logger* Logger::GlobalInstance() { |
| ZX_DEBUG_ASSERT(HasGlobalInstance()); |
| return g_instance.load(); |
| } |
| |
| void Logger::SetGlobalInstance(Logger* logger) { g_instance = logger; } |
| |
| bool Logger::HasGlobalInstance() { return g_instance != nullptr; } |
| |
| Logger::~Logger() = default; |
| |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| void Logger::HandleInterest(FidlInterest interest) { |
| if (interest.has_min_severity()) { |
| switch (interest.min_severity()) { |
| case FidlSeverity::kTrace: |
| severity_ = FUCHSIA_LOG_TRACE; |
| return; |
| case FidlSeverity::kDebug: |
| severity_ = FUCHSIA_LOG_DEBUG; |
| return; |
| case FidlSeverity::kInfo: |
| severity_ = FUCHSIA_LOG_INFO; |
| return; |
| case FidlSeverity::kWarn: |
| severity_ = FUCHSIA_LOG_WARNING; |
| return; |
| case FidlSeverity::kError: |
| severity_ = FUCHSIA_LOG_ERROR; |
| return; |
| case FidlSeverity::kFatal: |
| severity_ = FUCHSIA_LOG_FATAL; |
| return; |
| #if FUCHSIA_API_LEVEL_AT_LEAST(27) |
| default: |
| severity_ = FUCHSIA_LOG_INFO; |
| return; |
| #endif |
| } |
| } else { |
| severity_ = default_severity_; |
| } |
| } |
| |
| void Logger::OnInterestChange( |
| fidl::WireUnownedResult<fuchsia_logger::LogSink::WaitForInterestChange>& result) { |
| if (result.ok()) { |
| HandleInterest(result->value()->data); |
| log_sink_->WaitForInterestChange().Then(fit::bind_member(this, &Logger::OnInterestChange)); |
| } |
| } |
| #endif |
| |
| uint32_t Logger::GetAndResetDropped() { |
| return dropped_logs_.exchange(0, std::memory_order_relaxed); |
| } |
| |
| FuchsiaLogSeverity Logger::GetSeverity() { |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| return severity_.load(std::memory_order_relaxed); |
| #else |
| return logger_.GetMinSeverity(); |
| #endif |
| } |
| |
| #if FUCHSIA_API_LEVEL_LESS_THAN(NEXT) |
| void Logger::SetSeverity(FuchsiaLogSeverity severity) { severity_.store(severity); } |
| #endif |
| |
| void Logger::logf(FuchsiaLogSeverity severity, const char* tag, const char* file, int line, |
| const char* msg, ...) { |
| va_list args; |
| va_start(args, msg); |
| logvf(severity, tag, file, line, msg, args); |
| va_end(args); |
| } |
| |
| namespace { |
| const char* StripDots(const char* path) { |
| while (strncmp(path, "../", 3) == 0) { |
| path += 3; |
| } |
| return path; |
| } |
| |
| const char* StripPath(const char* path) { |
| auto p = strrchr(path, '/'); |
| if (p) { |
| return p + 1; |
| } else { |
| return path; |
| } |
| } |
| |
| const char* StripFile(const char* file, FuchsiaLogSeverity severity) { |
| return severity > FUCHSIA_LOG_INFO ? StripDots(file) : StripPath(file); |
| } |
| } // namespace |
| |
| void Logger::logvf(FuchsiaLogSeverity severity, const char* tag, const char* file, int line, |
| const char* msg, va_list args) { |
| if (tag) { |
| std::string tag_str(tag); |
| logvf(severity, {&tag_str, 1}, file, line, msg, args); |
| } else { |
| logvf(severity, cpp20::span<std::string>(), file, line, msg, args); |
| } |
| } |
| |
| void Logger::logvf(FuchsiaLogSeverity severity, cpp20::span<std::string> tags, const char* file, |
| int line, const char* msg, va_list args) { |
| if (!file || line <= 0) { |
| // We require a file and line number for printf-style logs. |
| return; |
| } |
| if (severity < GetSeverity()) { |
| return; |
| } |
| uint32_t dropped = dropped_logs_.exchange(0, std::memory_order_relaxed); |
| 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 < 0) { |
| // Invalid arguments -- we don't support logging empty strings |
| // for legacy printf-style messages. |
| return; |
| } |
| |
| if (count >= n) { |
| // truncated |
| constexpr char kEllipsis[] = "..."; |
| constexpr size_t kEllipsisSize = sizeof(kEllipsis); |
| snprintf(fmt_string + kFormatStringLength - 1 - kEllipsisSize, kEllipsisSize, kEllipsis); |
| } |
| |
| file = StripFile(file, severity); |
| flog::LogBuffer buffer; |
| BeginRecord(buffer, severity, file, line, fmt_string, dropped); |
| for (const auto& tag : tags) { |
| buffer.WriteKeyValue("tag", tag); |
| } |
| FlushRecord(buffer, dropped); |
| |
| if (severity == FUCHSIA_LOG_FATAL) { |
| abort(); |
| } |
| } |
| |
| #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::vlog(FuchsiaLogSeverity severity, const char* tag, const char* file, int line, |
| std::string_view fmt, std::format_args args) { |
| if (severity < GetSeverity()) { |
| return; |
| } |
| 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; |
| } |
| |
| uint32_t dropped = dropped_logs_.exchange(0, std::memory_order_relaxed); |
| |
| 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); |
| flog::LogBuffer buffer; |
| BeginRecord(buffer, severity, file, line, |
| std::string_view(fmt_buffer.data(), std::min(actual_size, kFormatStringLength)), |
| dropped); |
| if (tag) { |
| buffer.WriteKeyValue("tag", tag); |
| } |
| FlushRecord(buffer, dropped); |
| |
| if (severity == FUCHSIA_LOG_FATAL) { |
| abort(); |
| } |
| } |
| #endif |
| |
| } // namespace fdf |