| // 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, unsigned 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, unsigned 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, unsigned 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, | |
| unsigned 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, | |
| unsigned 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, unsigned 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, unsigned 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, unsigned 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 |