blob: 254cc58e8a781e5c00c63fc367363bcb699cd554 [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 <fuchsia/diagnostics/stream/cpp/fidl.h>
#include <fuchsia/logger/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fit/variant.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/streams/cpp/encode.h>
#include <lib/zx/channel.h>
#include <lib/zx/process.h>
#include <fstream>
#include <iostream>
#include <sstream>
namespace syslog_backend {
namespace {
const size_t kMaxTags = 4; // Legacy from ulib/syslog. Might be worth rethinking.
const char kMessageFieldName[] = "message";
const char kPidFieldName[] = "pid";
const char kTidFieldName[] = "tid";
const char kDroppedLogsFieldName[] = "dropped_logs";
const char kTagFieldName[] = "tag";
const char kFileFieldName[] = "file";
const char kLineFieldName[] = "line";
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;
}
template <typename T>
std::string FullMessageString(const char* condition, const T& msg);
template <>
std::string FullMessageString(const char* condition, const std::string& msg) {
std::ostringstream stream;
if (condition)
stream << "Check failed: " << condition << ". ";
stream << msg;
return stream.str();
}
template <>
std::string FullMessageString(const char* condition, const syslog::LogValue& msg) {
return FullMessageString(condition, msg.ToString());
}
void WriteValueToRecordWithKey(::fuchsia::diagnostics::stream::Record* record,
const std::string& key, const syslog::LogValue& value) {
auto& message = record->arguments.emplace_back();
message.name = key;
if (!value) {
return;
}
if (auto string_value = value.string_value()) {
message.value.WithText(std::string(*string_value));
} else if (auto int_value = value.int_value()) {
message.value.WithSignedInt(int64_t(*int_value));
} else {
// TODO(fxbug.dev/57571): LogValue also supports lists and nested objects, which Record doesn't.
// It does NOT support unsigned values, or floats, which Record does.
message.value.WithText(value.ToString());
}
}
template <typename T>
void WriteMessageToRecord(::fuchsia::diagnostics::stream::Record* record, const T& msg);
template <>
void WriteMessageToRecord(::fuchsia::diagnostics::stream::Record* record, const std::string& msg) {
auto& message = record->arguments.emplace_back();
message.name = kMessageFieldName;
message.value.WithText(std::string(msg));
}
template <>
void WriteMessageToRecord(::fuchsia::diagnostics::stream::Record* record,
const syslog::LogValue& msg) {
if (auto fields = msg.fields()) {
for (const auto& field : *fields) {
WriteValueToRecordWithKey(record, field.key(), field.value());
}
} else {
WriteValueToRecordWithKey(record, "message", msg);
}
}
} // namespace
class LogState {
public:
static void Set(const syslog::LogSettings& settings);
static void Set(const syslog::LogSettings& settings,
const std::initializer_list<std::string>& tags);
static const LogState& Get();
syslog::LogSeverity min_severity() const { return min_severity_; }
template <typename T>
void WriteLog(syslog::LogSeverity severity, const char* file_name, int line, const char* tag,
const char* condition, const T& msg) const;
private:
LogState(const syslog::LogSettings& settings, const std::initializer_list<std::string>& tags);
template <typename T>
bool WriteLogToSocket(const zx::socket* socket, zx_time_t time, zx_koid_t pid, zx_koid_t tid,
syslog::LogSeverity severity, const char* file_name, int line,
const char* tag, const char* condition, const T& msg) const;
bool WriteLogToFile(std::ofstream* file_ptr, zx_time_t time, zx_koid_t pid, zx_koid_t tid,
syslog::LogSeverity severity, const char* file_name, int line,
const char* tag, const char* condition, const std::string& msg) const;
syslog::LogSeverity min_severity_;
zx_koid_t pid_;
mutable fit::variant<zx::socket, std::ofstream> descriptor_ = zx::socket();
std::string tags_[kMaxTags];
std::string tag_str_;
size_t num_tags_ = 0;
};
template <typename T>
bool LogState::WriteLogToSocket(const zx::socket* socket, zx_time_t time, zx_koid_t pid,
zx_koid_t tid, syslog::LogSeverity severity, const char* file_name,
int line, const char* tag, const char* condition,
const T& msg) const {
::fuchsia::diagnostics::stream::Record record;
record.severity = ::fuchsia::diagnostics::Severity(severity);
record.timestamp = time;
auto& pid_arg = record.arguments.emplace_back();
pid_arg.name = kPidFieldName;
pid_arg.value.WithUnsignedInt(uint64_t(pid));
auto& tid_arg = record.arguments.emplace_back();
tid_arg.name = kTidFieldName;
tid_arg.value.WithUnsignedInt(uint64_t(tid));
auto dropped_count = GetAndResetDropped();
if (dropped_count) {
auto& dropped = record.arguments.emplace_back();
dropped.name = kDroppedLogsFieldName;
dropped.value.WithUnsignedInt(dropped_count);
}
for (size_t i = 0; i < num_tags_; i++) {
auto& tag_arg = record.arguments.emplace_back();
tag_arg.name = kTagFieldName;
tag_arg.value.WithText(std::string(tags_[i]));
}
if (tag) {
auto& tag_arg = record.arguments.emplace_back();
tag_arg.name = kTagFieldName;
tag_arg.value.WithText(tag);
}
// TODO(fxbug.dev/56051): Enable this everywhere once doing so won't spam everything.
if (severity >= syslog::LOG_ERROR) {
auto& file = record.arguments.emplace_back();
file.name = kFileFieldName;
file.value.WithText(std::string(file_name));
auto& line_arg = record.arguments.emplace_back();
line_arg.name = kLineFieldName;
line_arg.value.WithUnsignedInt(uint64_t(line));
}
WriteMessageToRecord(&record, msg);
std::vector<uint8_t> encoded;
streams::log_record(record, &encoded);
auto status = socket->write(0, encoded.data(), encoded.size(), nullptr);
if (status != ZX_OK) {
AddDropped(dropped_count + 1);
}
return status != ZX_ERR_BAD_STATE && status != ZX_ERR_PEER_CLOSED &&
severity != syslog::LOG_FATAL;
}
bool LogState::WriteLogToFile(std::ofstream* file_ptr, zx_time_t time, zx_koid_t pid, zx_koid_t tid,
syslog::LogSeverity severity, const char* file_name, int line,
const char* tag, const char* condition,
const std::string& msg) const {
auto& file = *file_ptr;
file << "[" << std::setw(5) << std::setfill('0') << time / 1000000000UL << "." << std::setw(6)
<< (time / 1000UL) % 1000000UL << std::setw(0) << "][" << pid << "][" << tid << "][";
auto& tag_str = tag_str_;
file << tag_str;
if (tag) {
if (!tag_str.empty()) {
file << ", ";
}
file << tag;
}
file << "] ";
switch (severity) {
case syslog::LOG_TRACE:
file << "TRACE";
break;
case syslog::LOG_DEBUG:
file << "DEBUG";
break;
case syslog::LOG_INFO:
file << "INFO";
break;
case syslog::LOG_WARNING:
file << "WARNING";
break;
case syslog::LOG_ERROR:
file << "ERROR";
break;
case syslog::LOG_FATAL:
file << "FATAL";
break;
default:
file << "VLOG(" << (syslog::LOG_INFO - severity) << ")";
}
file << ": [" << file_name << "(" << line << ")] " << msg << std::endl;
return severity != syslog::LOG_FATAL;
}
const LogState& LogState::Get() {
auto state = GetState();
if (!state) {
Set(syslog::LogSettings());
state = GetState();
}
return *state;
}
void LogState::Set(const syslog::LogSettings& settings) {
char process_name[ZX_MAX_NAME_LEN] = "";
zx_status_t status =
zx::process::self()->get_property(ZX_PROP_NAME, process_name, sizeof(process_name));
if (status != ZX_OK)
process_name[0] = '\0';
Set(settings, {process_name});
}
void LogState::Set(const syslog::LogSettings& settings,
const std::initializer_list<std::string>& tags) {
if (auto old = SetState(new LogState(settings, tags))) {
delete old;
}
}
LogState::LogState(const syslog::LogSettings& settings,
const std::initializer_list<std::string>& tags)
: min_severity_(settings.min_log_level), pid_(GetKoid(zx_process_self())) {
min_severity_ = settings.min_log_level;
std::ostringstream tag_str;
for (auto& tag : tags) {
if (num_tags_) {
tag_str << ", ";
}
tag_str << tag;
tags_[num_tags_++] = tag;
if (num_tags_ >= kMaxTags)
break;
}
tag_str_ = tag_str.str();
std::ofstream file;
if (!settings.log_file.empty()) {
file.open(settings.log_file, std::ios::out | std::ios::app);
}
if (file.is_open()) {
descriptor_ = std::move(file);
} else {
zx::channel logger, logger_request;
if (zx::channel::create(0, &logger, &logger_request) != ZX_OK) {
return;
}
::fuchsia::logger::LogSink_SyncProxy logger_client(std::move(logger));
if (fdio_service_connect("/svc/fuchsia.logger.LogSink", logger_request.release()) != ZX_OK) {
return;
}
zx::socket local, remote;
if (zx::socket::create(ZX_SOCKET_DATAGRAM, &local, &remote) != ZX_OK) {
return;
}
auto result = logger_client.ConnectStructured(std::move(remote));
if (result != ZX_OK) {
return;
}
descriptor_ = std::move(local);
}
}
template <typename T>
void LogState::WriteLog(syslog::LogSeverity severity, const char* file_name, int line,
const char* tag, const char* condition, const T& msg) const {
zx_koid_t tid = GetCurrentThreadKoid();
zx_time_t time = zx_clock_get_monotonic();
// Cached getter for a stringified version of the log message, so we stringify at most once.
auto msg_str = [as_str = std::string(), condition, &msg]() mutable -> const std::string& {
if (as_str.empty()) {
as_str = FullMessageString(condition, msg);
}
return as_str;
};
if (fit::holds_alternative<std::ofstream>(descriptor_)) {
auto& file = fit::get<std::ofstream>(descriptor_);
if (WriteLogToFile(&file, time, pid_, tid, severity, file_name, line, tag, condition,
msg_str())) {
return;
}
} else if (fit::holds_alternative<zx::socket>(descriptor_)) {
auto& socket = fit::get<zx::socket>(descriptor_);
if (WriteLogToSocket(&socket, time, pid_, tid, severity, file_name, line, tag, condition,
msg)) {
return;
}
}
std::cerr << msg_str() << std::endl;
}
void SetLogSettings(const syslog::LogSettings& settings) { LogState::Set(settings); }
void SetLogSettings(const syslog::LogSettings& settings,
const std::initializer_list<std::string>& tags) {
LogState::Set(settings, tags);
}
syslog::LogSeverity GetMinLogLevel() { return LogState::Get().min_severity(); }
void WriteLogValue(syslog::LogSeverity severity, const char* file_name, int line, const char* tag,
const char* condition, const syslog::LogValue& msg) {
LogState::Get().WriteLog(severity, file_name, line, tag, condition, msg);
}
void WriteLog(syslog::LogSeverity severity, const char* file_name, int line, const char* tag,
const char* condition, const std::string& msg) {
LogState::Get().WriteLog(severity, file_name, line, tag, condition, msg);
}
} // namespace syslog_backend