blob: eaba299405ac58425a7f1e1136a0ee3a4bcf84a3 [file] [log] [blame]
// Copyright 2023 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.diagnostics/cpp/natural_types.h>
#include <lib/diagnostics/reader/cpp/constants.h>
#include <lib/diagnostics/reader/cpp/logs.h>
#include <lib/fpromise/promise.h>
#include <lib/fpromise/scope.h>
#include <src/lib/fsl/vmo/strings.h>
namespace diagnostics::reader {
namespace {
inline fuchsia_diagnostics_types::Severity StringToSeverity(const std::string& input) {
if (strcasecmp(input.c_str(), "trace") == 0) {
return fuchsia_diagnostics_types::Severity::kTrace;
}
if (strcasecmp(input.c_str(), "debug") == 0) {
return fuchsia_diagnostics_types::Severity::kDebug;
}
if (strcasecmp(input.c_str(), "info") == 0) {
return fuchsia_diagnostics_types::Severity::kInfo;
}
if (strcasecmp(input.c_str(), "warn") == 0) {
return fuchsia_diagnostics_types::Severity::kWarn;
}
if (strcasecmp(input.c_str(), "error") == 0) {
return fuchsia_diagnostics_types::Severity::kError;
}
if (strcasecmp(input.c_str(), "fatal") == 0) {
return fuchsia_diagnostics_types::Severity::kFatal;
}
// We must never get here as long as we are reading data from Archivist.
return fuchsia_diagnostics_types::Severity::kInfo;
}
} // namespace
LogsSubscription::LogsSubscription(fidl::SharedClient<fuchsia_diagnostics::BatchIterator> iterator,
async::Executor& executor)
: iterator_(std::move(iterator)),
pending_(std::make_shared<std::deque<LogsData>>()),
done_(std::make_shared<bool>(false)) {}
bool LogsSubscription::Done() { return *done_; }
LogsSubscription::Promise LogsSubscription::Next() {
return fpromise::make_promise([this] { return ReadBatch(); }).wrap_with(scope_);
}
LogsSubscription::Promise LogsSubscription::ReadBatch() {
fpromise::bridge<std::optional<LogsData>, std::string> bridge;
if (!pending_->empty()) {
auto result = std::make_optional(std::move(pending_->front()));
pending_->pop_front();
return fpromise::make_result_promise<std::optional<LogsData>, std::string>(
fpromise::ok(std::move(result)));
} else if (*done_) {
return fpromise::make_result_promise<std::optional<LogsData>, std::string>(
fpromise::ok(std::nullopt));
}
iterator_->GetNext().Then([completer = std::move(bridge.completer), done = done_,
pending = pending_](auto& result) mutable {
if (result.is_error()) {
completer.complete_error("Batch iterator returned error: " +
result.error_value().FormatDescription());
return;
}
if (result->batch().empty()) {
*done = true;
completer.complete_ok(std::nullopt);
return;
}
for (auto& content : result->batch()) {
if (content.Which() != fuchsia_diagnostics::FormattedContent::Tag::kJson) {
completer.complete_error("Received an unexpected content format");
return;
}
std::string json;
fsl::SizedVmo vmo(std::move(content.json()->vmo()), content.json()->size());
if (!fsl::StringFromVmo(vmo, &json)) {
completer.complete_error("Failed to read returned VMO");
return;
}
rapidjson::Document document;
document.Parse(json);
auto maybe_json = LoadJson(std::move(document), pending, done);
if (maybe_json) {
pending->push_front(std::move(*maybe_json));
}
}
if (!pending->empty()) {
auto result = std::make_optional(std::move(pending->front()));
pending->pop_front();
completer.complete_ok(std::move(result));
}
});
return bridge.consumer.promise_or(fpromise::error("Failed to obtain consumer promise"));
}
std::optional<LogsData> LogsSubscription::LoadJson(rapidjson::Document document,
std::shared_ptr<std::deque<LogsData>> pending,
std::shared_ptr<bool> done) {
if (document.IsArray()) {
for (auto& value : document.GetArray()) {
// We need to ensure that the value is safely moved between documents, which may involve
// copying.
//
// It is an error to maintain a reference to a Value in a Document after that Document is
// destroyed, and the input |document| is destroyed immediately after this branch.
rapidjson::Document value_document;
rapidjson::Value temp(value.Move(), value_document.GetAllocator());
value_document.Swap(temp);
pending->push_back(LogsData(std::move(value_document)));
}
} else {
pending->push_back(LogsData(std::move(document)));
}
if (pending->empty()) {
return std::nullopt;
}
auto result = std::make_optional(std::move(pending->front()));
pending->pop_front();
return result;
}
LogsData::LogsData(rapidjson::Document document) {
if (document.HasMember(kMonikerName) && document[kMonikerName].IsString()) {
std::string val = document[kMonikerName].GetString();
moniker_ = document[kMonikerName].GetString();
}
if (document.HasMember(kVersionName) && document[kVersionName].IsNumber()) {
version_ = document[kVersionName].GetInt64();
} else {
version_ = 0;
}
if (document.HasMember(kMetadataName) && document[kMetadataName].IsObject()) {
const auto& metadata = document[kMetadataName].GetObject();
if (metadata.HasMember(kMetadataComponentURL) && metadata[kMetadataComponentURL].IsString()) {
metadata_.component_url = metadata[kMetadataComponentURL].GetString();
}
if (metadata.HasMember(kMetadataTimestamp) && metadata[kMetadataTimestamp].IsUint64()) {
metadata_.timestamp = metadata[kMetadataTimestamp].GetUint64();
}
if (metadata.HasMember(kMetadataSeverity) && metadata[kMetadataSeverity].IsString()) {
metadata_.severity = StringToSeverity(metadata[kMetadataSeverity].GetString());
}
if (metadata.HasMember(kMetadataTags) && metadata[kMetadataTags].IsArray()) {
const auto& tags = metadata[kMetadataTags].GetArray();
for (auto tag = tags.Begin(); tag != tags.End(); ++tag) {
if (tag->IsString()) {
metadata_.tags.push_back(tag->GetString());
}
}
}
if (metadata.HasMember(kMetadataPid) && metadata[kMetadataPid].IsUint64()) {
metadata_.pid = std::make_optional(metadata[kMetadataPid].GetUint64());
}
if (metadata.HasMember(kMetadataTid) && metadata[kMetadataTid].IsUint64()) {
metadata_.tid = std::make_optional(metadata[kMetadataTid].GetUint64());
}
if (metadata.HasMember(kMetadataFile) && metadata[kMetadataFile].IsString()) {
metadata_.file = std::make_optional(metadata[kMetadataFile].GetString());
}
if (metadata.HasMember(kMetadataLine) && metadata[kMetadataLine].IsUint64()) {
metadata_.line = std::make_optional(metadata[kMetadataLine].GetUint64());
}
if (metadata.HasMember(kMetadataErrors) && metadata[kMetadataErrors].IsArray()) {
const auto& errors = metadata[kMetadataErrors].GetArray();
for (auto item = errors.Begin(); item != errors.End(); ++item) {
if (!item->IsObject()) {
continue;
}
const auto& error = item->GetObject();
if (error.HasMember(kErrorDroppedLogs)) {
const auto& dropped = error[kErrorDroppedLogs];
if (dropped.IsObject() && dropped.HasMember(kCount) && dropped[kCount].IsUint64()) {
LogsData::Error error{DroppedLogsError{dropped[kCount].GetUint64()}};
metadata_.errors.push_back(std::move(error));
}
} else if (error.HasMember(kErrorRolledOutLogs)) {
const auto& rolled_out = error[kErrorRolledOutLogs];
if (rolled_out.IsObject() && rolled_out.HasMember(kCount) &&
rolled_out[kCount].IsUint64()) {
LogsData::Error error{RolledOutLogsError{rolled_out[kCount].GetUint64()}};
metadata_.errors.push_back(std::move(error));
}
} else if (error.HasMember(kErrorParseRecord)) {
const auto& failed_to_parse_record = error[kErrorParseRecord];
if (failed_to_parse_record.IsString()) {
LogsData::Error error{FailedToParseRecordError{failed_to_parse_record.GetString()}};
metadata_.errors.push_back(std::move(error));
}
} else if (error.HasMember(kErrorOther)) {
const auto& other = error[kErrorOther];
if (other.IsObject() && other.HasMember(kMessage) && other[kMessage].IsString()) {
LogsData::Error error{OtherError{other[kMessage].GetString()}};
metadata_.errors.push_back(std::move(error));
}
}
}
}
}
if (document.HasMember(kPayloadName) && document[kPayloadName].IsObject()) {
const auto& payload = document[kPayloadName].GetObject();
if (payload.HasMember(kPayloadRoot) && payload[kPayloadRoot].IsObject()) {
const auto& root = payload[kPayloadRoot].GetObject();
if (root.HasMember(kMessage) && root[kMessage].IsObject()) {
const auto& message = root[kMessage].GetObject();
if (message.HasMember(kPayloadMessageValue) && message[kPayloadMessageValue].IsString()) {
message_ = message[kPayloadMessageValue].GetString();
}
}
if (root.HasMember(kPayloadKeys) && root[kPayloadKeys].IsObject()) {
const auto& keys = root[kPayloadKeys].GetObject();
for (auto it = keys.MemberBegin(); it != keys.MemberEnd(); ++it) {
auto name = it->name.GetString();
switch (it->value.GetType()) {
case rapidjson::kNullType:
break;
case rapidjson::kFalseType:
case rapidjson::kTrueType:
keys_.emplace_back(
inspect::PropertyValue(name, inspect::BoolPropertyValue(it->value.GetBool())));
break;
case rapidjson::kStringType:
keys_.emplace_back(inspect::PropertyValue(
name, inspect::StringPropertyValue(it->value.GetString())));
break;
case rapidjson::kNumberType:
if (it->value.IsInt64()) {
keys_.emplace_back(
inspect::PropertyValue(name, inspect::IntPropertyValue(it->value.GetInt64())));
} else if (it->value.IsUint64()) {
keys_.emplace_back(inspect::PropertyValue(
name, inspect::UintPropertyValue(it->value.GetUint64())));
} else {
keys_.emplace_back(inspect::PropertyValue(
name, inspect::DoublePropertyValue(it->value.GetDouble())));
}
break;
case rapidjson::kArrayType:
LoadArray(name, it->value.GetArray());
break;
default:
break;
}
}
}
}
}
}
void LogsData::LoadArray(const std::string& name, const rapidjson::Value::Array& arr) {
if (arr.Empty()) {
keys_.emplace_back(inspect::PropertyValue(
name, inspect::IntArrayValue(std::vector<int64_t>{}, inspect::ArrayDisplayFormat::kFlat)));
return;
}
switch (arr.Begin()->GetType()) {
case rapidjson::kStringType: {
std::vector<std::string> values;
for (auto& v : arr) {
values.emplace_back(v.GetString());
}
keys_.emplace_back(inspect::PropertyValue(
name, inspect::StringArrayValue(std::move(values), inspect::ArrayDisplayFormat::kFlat)));
break;
}
case rapidjson::kNumberType: {
if (arr.Begin()->IsInt64()) {
std::vector<std::int64_t> values;
for (auto& v : arr) {
values.emplace_back(v.GetInt64());
}
keys_.emplace_back(inspect::PropertyValue(
name, inspect::IntArrayValue(std::move(values), inspect::ArrayDisplayFormat::kFlat)));
} else if (arr.Begin()->IsUint64()) {
std::vector<std::uint64_t> values;
for (auto& v : arr) {
values.emplace_back(v.GetUint64());
}
keys_.emplace_back(inspect::PropertyValue(
name, inspect::UintArrayValue(std::move(values), inspect::ArrayDisplayFormat::kFlat)));
} else if (arr.Begin()->IsDouble()) {
std::vector<double> values;
for (auto& v : arr) {
values.emplace_back(v.GetDouble());
}
keys_.emplace_back(inspect::PropertyValue(
name,
inspect::DoubleArrayValue(std::move(values), inspect::ArrayDisplayFormat::kFlat)));
}
break;
}
default:
break;
}
}
} // namespace diagnostics::reader