blob: 913e8f653ead581c3f5fc5805761fdc573ee6634 [file] [log] [blame]
// 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 <assert.h>
#include <fidl/fuchsia.diagnostics.types/cpp/fidl.h>
#include <fidl/fuchsia.diagnostics/cpp/fidl.h>
#include <fidl/fuchsia.logger/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async-loop/loop.h>
#include <lib/async/cpp/executor.h>
#include <lib/async/default.h>
#include <lib/syslog/cpp/log_level.h>
#include <lib/syslog/cpp/log_settings.h>
#if FUCHSIA_API_LEVEL_AT_LEAST(NEXT)
#include <lib/syslog/cpp/log_settings_internal.h>
#endif
#include <lib/syslog/cpp/logging_backend_fuchsia_globals.h>
#include <lib/syslog/structured_backend/cpp/log_buffer.h>
#include <lib/syslog/structured_backend/cpp/raw_log_settings.h>
#include <lib/zx/channel.h>
#include <lib/zx/process.h>
#include <cstddef>
#include <fstream>
#include <iostream>
#include <sstream>
#include <variant>
#include "lib/component/incoming/cpp/protocol.h"
#include "lib/syslog/cpp/macros.h"
namespace fuchsia_logging {
namespace {
#if FUCHSIA_API_LEVEL_AT_LEAST(27)
using FidlInterest = fuchsia_diagnostics_types::wire::Interest;
#else
using FidlInterest = fuchsia_diagnostics::wire::Interest;
#endif
} // namespace
#if FUCHSIA_API_LEVEL_LESS_THAN(NEXT)
class GlobalStateLock;
namespace internal {
class LogState {
public:
static void Set(const fuchsia_logging::LogSettings& settings, const GlobalStateLock& lock);
fuchsia_logging::RawLogSeverity min_severity() const { return min_severity_; }
const std::vector<std::string>& tags() const { return tags_; }
// Allowed to be const because descriptor_ is mutable
std::variant<zx::socket, std::ofstream>& descriptor() const { return logsink_socket_; }
private:
explicit LogState(const fuchsia_logging::LogSettings& settings);
void Connect();
void PollInterest();
void HandleInterest(FidlInterest interest);
fidl::WireSharedClient<fuchsia_logger::LogSink> log_sink_;
void (*on_severity_changed_)(fuchsia_logging::RawLogSeverity severity);
// Loop that never runs any code, but is needed so FIDL
// doesn't crash if we have no dispatcher thread.
async::Loop unused_loop_;
std::atomic<fuchsia_logging::RawLogSeverity> min_severity_;
const fuchsia_logging::RawLogSeverity default_severity_;
mutable std::variant<zx::socket, std::ofstream> logsink_socket_ = zx::socket();
std::vector<std::string> tags_;
async_dispatcher_t* interest_listener_dispatcher_;
fuchsia_logging::InterestListenerBehavior interest_listener_config_;
// Handle to a fuchsia.logger.LogSink instance.
std::optional<fidl::ClientEnd<fuchsia_logger::LogSink>> provided_log_sink_;
};
} // namespace internal
// Global state lock. In order to mutate the LogState through SetStateLocked
// and GetStateLocked you must hold this capability.
// Do not directly use the C API. The C API exists solely
// to expose globals as a shared library.
// If the logger is not initialized, this will implicitly init the logger.
class GlobalStateLock {
public:
GlobalStateLock(bool autoinit = true) {
internal::FuchsiaLogAcquireState();
if (autoinit && !internal::FuchsiaLogGetStateLocked()) {
internal::LogState::Set(fuchsia_logging::LogSettings(), *this);
}
}
// Retrieves the global state
internal::LogState* operator->() const { return internal::FuchsiaLogGetStateLocked(); }
// Sets the global state
void Set(internal::LogState* state) const { FuchsiaLogSetStateLocked(state); }
// Retrieves the global state
internal::LogState* operator*() const { return internal::FuchsiaLogGetStateLocked(); }
~GlobalStateLock() { internal::FuchsiaLogReleaseState(); }
};
#endif // FUCHSIA_API_LEVEL_LESS_THAN(NEXT)
namespace {
zx_koid_t ProcessSelfKoid() {
zx_info_handle_basic_t info;
// We need to use _zx_object_get_info to avoid breaking the driver ABI.
// fake_ddk can fake out this method, which results in us deadlocking
// when used in certain drivers because the fake doesn't properly pass-through
// to the real syscall in this case.
zx_status_t status = _zx_object_get_info(zx_process_self(), ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
}
zx_koid_t globalPid = ProcessSelfKoid();
const char kTagFieldName[] = "tag";
#if FUCHSIA_API_LEVEL_AT_LEAST(NEXT)
void FixDefaultSettings(RawLogSettings& settings, bool interest_listener_enabled) {
// If now handle is provided, try the incoming namespace.
if (settings.log_sink == ZX_HANDLE_INVALID) {
auto connect_result = component::Connect<fuchsia_logger::LogSink>();
// If there is an error, we leave `log_sink` with ZX_HANDLE_INVALID which will result in a no-op
// logger later.
if (connect_result.is_ok()) {
settings.log_sink = connect_result->TakeChannel().release();
}
}
// If no dispatcher specified, try the default dispatcher.
if (!settings.dispatcher && interest_listener_enabled) {
settings.dispatcher = async_get_default_dispatcher();
}
}
RawLogSettings GetDefaultSettings() {
RawLogSettings settings;
FixDefaultSettings(settings, true);
return settings;
}
#endif
void BeginRecordInternal(LogBuffer* buffer, fuchsia_logging::RawLogSeverity severity,
std::optional<std::string_view> file_name, unsigned int line,
std::optional<std::string_view> msg,
std::optional<std::string_view> condition) {
// Optional so no allocation overhead occurs if condition isn't set.
std::optional<std::string> modified_msg;
if (condition) {
std::stringstream s;
s << "Check failed: " << *condition << ". ";
if (msg) {
s << *msg;
}
modified_msg = s.str();
msg = modified_msg->data();
}
buffer->BeginRecord(severity, file_name, line, msg, 0, globalPid,
internal::FuchsiaLogGetCurrentThreadKoid());
#if FUCHSIA_API_LEVEL_LESS_THAN(NEXT)
GlobalStateLock log_state;
for (size_t i = 0; i < log_state->tags().size(); i++) {
buffer->WriteKeyValue(kTagFieldName, log_state->tags()[i]);
}
#else
internal::FuchsiaLogForEachTag(
internal::FuchsiaLogGetGlobalLogger(GetDefaultSettings), buffer,
+[](void* context, const char* tag) {
auto buffer = reinterpret_cast<LogBuffer*>(context);
buffer->WriteKeyValue(kTagFieldName, tag);
});
#endif
}
void BeginRecord(LogBuffer* buffer, fuchsia_logging::RawLogSeverity severity,
internal::NullSafeStringView file, unsigned int line,
internal::NullSafeStringView msg, internal::NullSafeStringView condition) {
BeginRecordInternal(buffer, severity, file, line, msg, condition);
internal::SetFlushCallback(*buffer, [](cpp20::span<const uint8_t> data, FlushConfig config) {
#if FUCHSIA_API_LEVEL_LESS_THAN(NEXT)
zx::unowned_socket socket(std::get<0>(GlobalStateLock()->descriptor()));
return internal::FlushToSocket(std::move(socket), data, config);
#else
// We ignore `block_if_full` because we won't be supporting it with IOBuffers which is coming
// soon.
return internal::FuchsiaLogWrite(internal::FuchsiaLogGetGlobalLogger(GetDefaultSettings),
data.data(), data.size()) == ZX_OK;
#endif
});
}
void BeginRecordWithSocket(LogBuffer* buffer, fuchsia_logging::RawLogSeverity severity,
internal::NullSafeStringView file_name, unsigned int line,
internal::NullSafeStringView msg, internal::NullSafeStringView condition,
zx_handle_t socket) {
BeginRecordInternal(buffer, severity, file_name, line, msg, condition);
internal::SetFlushCallback(
*buffer, [socket](cpp20::span<const uint8_t> data, FlushConfig config) {
return internal::FlushToSocket(zx::unowned_socket(socket), data, config);
});
}
void SetLogSettings(const fuchsia_logging::LogSettings& settings) {
#if FUCHSIA_API_LEVEL_LESS_THAN(NEXT)
GlobalStateLock lock(false);
internal::LogState::Set(settings, lock);
#else
const bool interest_listener_enabled =
settings.interest_listener_config_ != InterestListenerBehavior::Disabled;
internal::WithRawSettings(settings, [interest_listener_enabled](RawLogSettings& raw_settings) {
FixDefaultSettings(raw_settings, interest_listener_enabled);
internal::FuchsiaLogInitGlobalLogger(&raw_settings);
});
#endif
}
} // namespace
#if FUCHSIA_API_LEVEL_LESS_THAN(NEXT)
void internal::LogState::PollInterest() {
log_sink_->WaitForInterestChange().Then(
[this](fidl::WireUnownedResult<fuchsia_logger::LogSink::WaitForInterestChange>&
interest_result) {
// FIDL can cancel the operation if the logger is being reconfigured
// which results in an error.
if (!interest_result.ok()) {
return;
}
HandleInterest(interest_result->value()->data);
on_severity_changed_(min_severity_);
PollInterest();
});
}
void internal::LogState::HandleInterest(FidlInterest interest) {
if (!interest.has_min_severity()) {
min_severity_ = default_severity_;
} else {
min_severity_ = static_cast<fuchsia_logging::RawLogSeverity>(interest.min_severity());
}
}
void internal::LogState::Connect() {
auto default_dispatcher = async_get_default_dispatcher();
bool missing_dispatcher = false;
if (interest_listener_config_ != fuchsia_logging::Disabled) {
if (!interest_listener_dispatcher_ && !default_dispatcher) {
missing_dispatcher = true;
}
}
// Regardless of whether or not we need to do anything async, FIDL async bindings
// require a valid dispatcher or they panic.
if (!interest_listener_dispatcher_) {
if (default_dispatcher) {
interest_listener_dispatcher_ = default_dispatcher;
} else {
interest_listener_dispatcher_ = unused_loop_.dispatcher();
}
}
fidl::ClientEnd<fuchsia_logger::LogSink> client_end;
if (!provided_log_sink_.has_value()) {
auto connect_result = component::Connect<fuchsia_logger::LogSink>();
if (connect_result.is_error()) {
return;
}
client_end = std::move(connect_result.value());
} else {
client_end = std::move(*provided_log_sink_);
}
log_sink_.Bind(std::move(client_end), interest_listener_dispatcher_);
if (interest_listener_config_ == fuchsia_logging::Enabled && !missing_dispatcher) {
auto interest_result = log_sink_.sync()->WaitForInterestChange();
if (!interest_result.ok()) {
// Logging isn't available. Silently drop logs.
return;
}
if (interest_result->is_ok()) {
HandleInterest(interest_result->value()->data);
}
on_severity_changed_(min_severity_);
}
zx::socket local, remote;
if (zx::socket::create(ZX_SOCKET_DATAGRAM, &local, &remote) != ZX_OK) {
return;
}
if (!log_sink_->ConnectStructured(std::move(remote)).ok()) {
return;
}
if (interest_listener_config_ != fuchsia_logging::Disabled) {
PollInterest();
}
logsink_socket_ = std::move(local);
}
void internal::LogState::Set(const fuchsia_logging::LogSettings& settings,
const GlobalStateLock& lock) {
auto old = *lock;
lock.Set(new LogState(settings));
if (old) {
delete old;
}
}
internal::LogState::LogState(const fuchsia_logging::LogSettings& settings)
: unused_loop_(&kAsyncLoopConfigNeverAttachToThread),
min_severity_(settings.min_log_level),
default_severity_(settings.min_log_level) {
interest_listener_dispatcher_ =
static_cast<async_dispatcher_t*>(settings.single_threaded_dispatcher);
interest_listener_config_ = settings.interest_listener_config_;
min_severity_ = settings.min_log_level;
if (settings.log_sink) {
provided_log_sink_ = fidl::ClientEnd<fuchsia_logger::LogSink>(zx::channel(settings.log_sink));
}
on_severity_changed_ = settings.severity_change_callback;
if (!on_severity_changed_) {
on_severity_changed_ = [](fuchsia_logging::RawLogSeverity severity) {};
}
for (auto& tag : settings.tags) {
tags_.push_back(tag);
}
Connect();
}
#endif // FUCHSIA_API_LEVEL_LESS_THAN(NEXT)
// Sets the default log severity. If not explicitly set,
// this defaults to INFO, or to the value specified by Archivist.
LogSettingsBuilder& LogSettingsBuilder::WithMinLogSeverity(
fuchsia_logging::RawLogSeverity min_log_level) {
settings_.min_log_level = min_log_level;
return *this;
}
// Disables the interest listener.
LogSettingsBuilder& LogSettingsBuilder::DisableInterestListener() {
WithInterestListenerConfiguration(fuchsia_logging::Disabled);
return *this;
}
// Disables waiting for the initial interest from Archivist.
// The level specified in SetMinLogSeverity or INFO will be used
// as the default.
LogSettingsBuilder& LogSettingsBuilder::DisableWaitForInitialInterest() {
if (settings_.interest_listener_config_ == fuchsia_logging::Enabled) {
WithInterestListenerConfiguration(fuchsia_logging::EnabledNonBlocking);
}
return *this;
}
#if FUCHSIA_API_LEVEL_LESS_THAN(NEXT)
LogSettingsBuilder& LogSettingsBuilder::WithSeverityChangedListener(
void (*callback)(fuchsia_logging::RawLogSeverity severity)) {
settings_.severity_change_callback = callback;
return *this;
}
#else
LogSettingsBuilder& LogSettingsBuilder::WithSeverityChangedListener(
void* context, void (*callback)(void*, fuchsia_logging::RawLogSeverity severity)) {
settings_.severity_change_callback = callback;
settings_.severity_change_callback_context = context;
return *this;
}
#endif
LogSettingsBuilder& LogSettingsBuilder::WithInterestListenerConfiguration(
InterestListenerBehavior config) {
settings_.interest_listener_config_ = config;
return *this;
}
// Sets the log sink handle.
LogSettingsBuilder& LogSettingsBuilder::WithLogSink(zx_handle_t log_sink) {
settings_.log_sink = log_sink;
return *this;
}
// Sets the dispatcher to use.
LogSettingsBuilder& LogSettingsBuilder::WithDispatcher(async_dispatcher_t* dispatcher) {
settings_.single_threaded_dispatcher = dispatcher;
return *this;
}
LogSettingsBuilder& LogSettingsBuilder::WithTags(const std::initializer_list<std::string>& tags) {
for (auto& tag : tags) {
settings_.tags.push_back(tag);
}
return *this;
}
// Configures the log settings.
void LogSettingsBuilder::BuildAndInitialize() { SetLogSettings(settings_); }
#if FUCHSIA_API_LEVEL_LESS_THAN(NEXT)
RawLogSeverity GetMinLogSeverity() { return GlobalStateLock()->min_severity(); }
#endif
LogBuffer LogBufferBuilder::Build() {
LogBuffer buffer;
if (socket_) {
fuchsia_logging::BeginRecordWithSocket(
&buffer, severity_,
fuchsia_logging::internal::NullSafeStringView::CreateFromOptional(file_name_), line_,
fuchsia_logging::internal::NullSafeStringView::CreateFromOptional(msg_),
fuchsia_logging::internal::NullSafeStringView::CreateFromOptional(condition_), socket_);
} else {
fuchsia_logging::BeginRecord(
&buffer, severity_,
fuchsia_logging::internal::NullSafeStringView::CreateFromOptional(file_name_), line_,
fuchsia_logging::internal::NullSafeStringView::CreateFromOptional(msg_),
fuchsia_logging::internal::NullSafeStringView::CreateFromOptional(condition_));
}
return buffer;
}
} // namespace fuchsia_logging