blob: 609cc3f387535e304945e8e6204cc3fbd5382f3f [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 <fuchsia/logger/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/loop.h>
#include <lib/async/cpp/executor.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/stdcompat/variant.h>
#include <lib/sync/completion.h>
#include <lib/syslog/cpp/log_level.h>
#include <lib/syslog/cpp/logging_backend.h>
#include <lib/syslog/cpp/logging_backend_fuchsia_globals.h>
#include <lib/syslog/structured_backend/cpp/fuchsia_syslog.h>
#include <lib/zx/channel.h>
#include <lib/zx/process.h>
#include <cstddef>
#include <fstream>
#include <iostream>
#include <sstream>
#include "lib/syslog/cpp/macros.h"
namespace syslog_runtime {
bool HasStructuredBackend() { return true; }
using log_word_t = uint64_t;
zx_koid_t GetKoid(zx_handle_t handle) {
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(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
}
static zx_koid_t pid = GetKoid(zx_process_self());
static thread_local zx_koid_t tid = FuchsiaLogGetCurrentThreadKoid();
struct RecordState {
// Message string -- valid if severity is FATAL. For FATAL
// logs the caller is responsible for ensuring the string
// is valid for the duration of the call (which our macros
// will ensure for current users). This must be a
// const char* as the type has to be trivially destructable.
// This will leak on usage, as the process will crash shortly afterwards.
const char* maybe_fatal_string;
fuchsia_syslog::LogBuffer buffer;
zx_handle_t socket;
FuchsiaLogSeverity raw_severity;
static RecordState* CreatePtr(LogBuffer* buffer) {
return reinterpret_cast<RecordState*>(&buffer->record_state);
}
};
static_assert(sizeof(RecordState) <= sizeof(LogBuffer::record_state) + sizeof(LogBuffer::data));
static_assert(offsetof(LogBuffer, data) ==
offsetof(LogBuffer, record_state) + sizeof(LogBuffer::record_state));
static_assert(std::alignment_of<RecordState>() == sizeof(uint64_t));
const size_t kMaxTags = 4; // Legacy from ulib/syslog. Might be worth rethinking.
const char kTagFieldName[] = "tag";
class GlobalStateLock;
class LogState {
public:
static void Set(const fuchsia_logging::LogSettings& settings, const GlobalStateLock& lock);
static void Set(const fuchsia_logging::LogSettings& settings,
const std::initializer_list<std::string>& tags, const GlobalStateLock& lock);
void set_severity_handler(void (*callback)(void* context, fuchsia_logging::LogSeverity severity),
void* context) {
handler_ = callback;
handler_context_ = context;
}
fuchsia_logging::LogSeverity min_severity() const { return min_severity_; }
const std::string* tags() const { return tags_; }
size_t tag_count() const { return num_tags_; }
// Allowed to be const because descriptor_ is mutable
cpp17::variant<zx::socket, std::ofstream>& descriptor() const { return descriptor_; }
void HandleInterest();
void Connect();
void ConnectAsync();
struct Task : public async_task_t {
LogState* context;
sync_completion_t completion;
};
static void RunTask(async_dispatcher_t* dispatcher, async_task_t* task, zx_status_t status) {
Task* callback = static_cast<Task*>(task);
callback->context->ConnectAsync();
sync_completion_signal(&callback->completion);
}
// Thunk to initialize logging and allocate HLCPP objects
// which are "thread-hostile" and cannot be allocated on a remote thread.
void InitializeAsyncTask() {
Task task = {};
task.deadline = 0;
task.handler = RunTask;
task.context = this;
async_post_task(executor_->dispatcher(), &task);
sync_completion_wait(&task.completion, ZX_TIME_INFINITE);
}
private:
LogState(const fuchsia_logging::LogSettings& settings,
const std::initializer_list<std::string>& tags);
fuchsia::logger::LogSinkPtr log_sink_;
void (*handler_)(void* context, fuchsia_logging::LogSeverity severity);
void* handler_context_;
async::Loop loop_;
std::optional<async::Executor> executor_;
std::atomic<fuchsia_logging::LogSeverity> min_severity_;
const fuchsia_logging::LogSeverity default_severity_;
mutable cpp17::variant<zx::socket, std::ofstream> descriptor_ = zx::socket();
std::string tags_[kMaxTags];
size_t num_tags_ = 0;
async_dispatcher_t* interest_listener_dispatcher_;
bool serve_interest_listener_;
bool wait_for_initial_interest_;
// Handle to a fuchsia.logger.LogSink instance.
zx_handle_t provided_log_sink_ = ZX_HANDLE_INVALID;
};
// 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() {
FuchsiaLogAcquireState();
if (!FuchsiaLogGetStateLocked()) {
LogState::Set(fuchsia_logging::LogSettings(), *this);
}
}
// Retrieves the global state
syslog_runtime::LogState* operator->() const { return FuchsiaLogGetStateLocked(); }
// Sets the global state
void Set(syslog_runtime::LogState* state) const { FuchsiaLogSetStateLocked(state); }
// Retrieves the global state
syslog_runtime::LogState* operator*() const { return FuchsiaLogGetStateLocked(); }
~GlobalStateLock() { FuchsiaLogReleaseState(); }
};
static fuchsia_logging::LogSeverity IntoLogSeverity(fuchsia::diagnostics::Severity severity) {
switch (severity) {
case fuchsia::diagnostics::Severity::TRACE:
return fuchsia_logging::LOG_TRACE;
case fuchsia::diagnostics::Severity::DEBUG:
return fuchsia_logging::LOG_DEBUG;
case fuchsia::diagnostics::Severity::INFO:
return fuchsia_logging::LOG_INFO;
case fuchsia::diagnostics::Severity::WARN:
return fuchsia_logging::LOG_WARNING;
case fuchsia::diagnostics::Severity::ERROR:
return fuchsia_logging::LOG_ERROR;
case fuchsia::diagnostics::Severity::FATAL:
return fuchsia_logging::LOG_FATAL;
}
}
void LogState::HandleInterest() {
log_sink_->WaitForInterestChange(
[=](fuchsia::logger::LogSink_WaitForInterestChange_Result interest_result) {
auto interest = std::move(interest_result.response().data);
if (!interest.has_min_severity()) {
min_severity_ = default_severity_;
} else {
min_severity_ = IntoLogSeverity(interest.min_severity());
}
handler_(handler_context_, min_severity_);
HandleInterest();
});
}
void LogState::ConnectAsync() {
zx::channel logger, logger_request;
if (zx::channel::create(0, &logger, &logger_request) != ZX_OK) {
return;
}
if (provided_log_sink_ == ZX_HANDLE_INVALID) {
// TODO(https://fxbug.dev/42154983): Support for custom names.
if (fdio_service_connect("/svc/fuchsia.logger.LogSink", logger_request.release()) != ZX_OK) {
return;
}
} else {
logger = zx::channel(provided_log_sink_);
provided_log_sink_ = ZX_HANDLE_INVALID;
}
if (wait_for_initial_interest_) {
fuchsia::logger::LogSinkSyncPtr sync_log_sink;
sync_log_sink.Bind(std::move(logger));
fuchsia::logger::LogSink_WaitForInterestChange_Result interest_result;
sync_log_sink->WaitForInterestChange(&interest_result);
auto interest = std::move(interest_result.response().data);
if (!interest.has_min_severity()) {
min_severity_ = default_severity_;
} else {
min_severity_ = IntoLogSeverity(interest.min_severity());
}
handler_(handler_context_, min_severity_);
logger = sync_log_sink.Unbind().TakeChannel();
}
if (log_sink_.Bind(std::move(logger), loop_.dispatcher()) != ZX_OK) {
return;
}
zx::socket local, remote;
if (zx::socket::create(ZX_SOCKET_DATAGRAM, &local, &remote) != ZX_OK) {
return;
}
log_sink_->ConnectStructured(std::move(remote));
HandleInterest();
descriptor_ = std::move(local);
}
void LogState::Connect() {
if (serve_interest_listener_) {
if (!interest_listener_dispatcher_) {
loop_.StartThread("log-interest-listener-thread");
} else {
executor_.emplace(interest_listener_dispatcher_);
}
handler_ = [](void* ctx, fuchsia_logging::LogSeverity severity) {};
InitializeAsyncTask();
} else {
zx::channel logger, logger_request;
if (provided_log_sink_ == ZX_HANDLE_INVALID) {
if (zx::channel::create(0, &logger, &logger_request) != ZX_OK) {
return;
}
// TODO(https://fxbug.dev/42154983): Support for custom names.
if (fdio_service_connect("/svc/fuchsia.logger.LogSink", logger_request.release()) != ZX_OK) {
return;
}
} else {
logger = zx::channel(provided_log_sink_);
}
::fuchsia::logger::LogSinkPtr logger_client;
logger_client.Bind(std::move(logger), loop_.dispatcher());
zx::socket local, remote;
if (zx::socket::create(ZX_SOCKET_DATAGRAM, &local, &remote) != ZX_OK) {
return;
}
logger_client->ConnectStructured(std::move(remote));
descriptor_ = std::move(local);
}
}
void SetInterestChangedListener(void (*callback)(void* context,
fuchsia_logging::LogSeverity severity),
void* context) {
GlobalStateLock log_state;
log_state->set_severity_handler(callback, context);
}
void BeginRecordInternal(LogBuffer* buffer, fuchsia_logging::LogSeverity severity,
cpp17::optional<cpp17::string_view> file_name, unsigned int line,
cpp17::optional<cpp17::string_view> msg,
cpp17::optional<cpp17::string_view> condition, zx_handle_t socket) {
// Ensure we have log state
GlobalStateLock log_state;
// 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();
if (severity == fuchsia_logging::LOG_FATAL) {
// We're crashing -- so leak the string in order to prevent
// use-after-free of the maybe_fatal_string.
// We need this to prevent a use-after-free in FlushRecord.
auto new_msg = new char[modified_msg->size() + 1];
strcpy(const_cast<char*>(new_msg), modified_msg->c_str());
msg = new_msg;
} else {
msg = modified_msg->data();
}
}
auto* state = RecordState::CreatePtr(buffer);
// Invoke the constructor of RecordState to construct a valid RecordState
// inside the LogBuffer.
new (state) RecordState;
state->raw_severity = severity;
if (socket == ZX_HANDLE_INVALID) {
socket = std::get<0>(log_state->descriptor()).get();
}
state->socket = socket;
if (severity == fuchsia_logging::LOG_FATAL) {
state->maybe_fatal_string = msg->data();
}
state->buffer.BeginRecord(severity, file_name, line, msg, zx::unowned_socket(socket), 0, pid,
tid);
for (size_t i = 0; i < log_state->tag_count(); i++) {
state->buffer.WriteKeyValue(kTagFieldName, log_state->tags()[i]);
}
}
void BeginRecord(LogBuffer* buffer, fuchsia_logging::LogSeverity severity, NullSafeStringView file,
unsigned int line, NullSafeStringView msg, NullSafeStringView condition) {
BeginRecordInternal(buffer, severity, file, line, msg, condition, ZX_HANDLE_INVALID);
}
void BeginRecordWithSocket(LogBuffer* buffer, fuchsia_logging::LogSeverity severity,
NullSafeStringView file_name, unsigned int line, NullSafeStringView msg,
NullSafeStringView condition, zx_handle_t socket) {
BeginRecordInternal(buffer, severity, file_name, line, msg, condition, socket);
}
void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, cpp17::string_view value) {
auto* state = RecordState::CreatePtr(buffer);
state->buffer.WriteKeyValue(key, value);
}
void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, int64_t value) {
auto* state = RecordState::CreatePtr(buffer);
state->buffer.WriteKeyValue(key, value);
}
void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, uint64_t value) {
auto* state = RecordState::CreatePtr(buffer);
state->buffer.WriteKeyValue(key, value);
}
void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, double value) {
auto* state = RecordState::CreatePtr(buffer);
state->buffer.WriteKeyValue(key, value);
}
void WriteKeyValue(LogBuffer* buffer, cpp17::string_view key, bool value) {
auto* state = RecordState::CreatePtr(buffer);
state->buffer.WriteKeyValue(key, value);
}
bool FlushRecord(LogBuffer* buffer) {
GlobalStateLock log_state;
auto* state = RecordState::CreatePtr(buffer);
if (state->raw_severity < log_state->min_severity()) {
return true;
}
auto ret = state->buffer.FlushRecord();
if (state->raw_severity == fuchsia_logging::LOG_FATAL) {
std::cerr << state->maybe_fatal_string << std::endl;
abort();
}
return ret;
}
void LogState::Set(const fuchsia_logging::LogSettings& settings, const GlobalStateLock& lock) {
Set(settings, {}, lock);
}
void LogState::Set(const fuchsia_logging::LogSettings& settings,
const std::initializer_list<std::string>& tags, const GlobalStateLock& lock) {
auto old = *lock;
lock.Set(new LogState(settings, tags));
if (old) {
delete old;
}
}
LogState::LogState(const fuchsia_logging::LogSettings& in_settings,
const std::initializer_list<std::string>& tags)
: loop_(&kAsyncLoopConfigNeverAttachToThread),
executor_(loop_.dispatcher()),
min_severity_(in_settings.min_log_level),
default_severity_(in_settings.min_log_level),
wait_for_initial_interest_(in_settings.wait_for_initial_interest) {
fuchsia_logging::LogSettings settings = in_settings;
interest_listener_dispatcher_ =
static_cast<async_dispatcher_t*>(settings.single_threaded_dispatcher);
serve_interest_listener_ = !settings.disable_interest_listener;
min_severity_ = in_settings.min_log_level;
provided_log_sink_ = in_settings.log_sink;
for (auto& tag : tags) {
tags_[num_tags_++] = tag;
if (num_tags_ >= kMaxTags)
break;
}
Connect();
}
void SetLogSettings(const fuchsia_logging::LogSettings& settings) {
GlobalStateLock lock;
LogState::Set(settings, lock);
}
void SetLogSettings(const fuchsia_logging::LogSettings& settings,
const std::initializer_list<std::string>& tags) {
GlobalStateLock lock;
LogState::Set(settings, tags, lock);
}
fuchsia_logging::LogSeverity GetMinLogLevel() {
GlobalStateLock lock;
return lock->min_severity();
}
} // namespace syslog_runtime