| // Copyright 2026 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 "src/developer/forensics/feedback_data/log_buffer.h" |
| |
| #include <lib/fit/defer.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "src/developer/forensics/feedback_data/constants.h" |
| #include "src/developer/forensics/feedback_data/log_source.h" |
| #include "src/developer/forensics/utils/log_format.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace forensics::feedback { |
| namespace { |
| |
| constexpr int32_t kDefaultLogSeverity = 0; |
| const std::vector<std::string> kDefaultTags = {}; |
| |
| size_t AppendRepeated(const size_t last_msg_repeated, std::string& append_to) { |
| const std::string repeated_str = |
| last_msg_repeated == 1 |
| ? feedback_data::kRepeatedOnceFormatStr |
| : fxl::StringPrintf(feedback_data::kRepeatedFormatStr, last_msg_repeated); |
| |
| append_to.append(repeated_str); |
| return repeated_str.size(); |
| } |
| |
| } // namespace |
| |
| LogBuffer::LogBuffer(const StorageSize capacity, RedactorBase* redactor) |
| : redactor_(redactor), capacity_(capacity.ToBytes()) {} |
| |
| std::optional<zx::time_boot> LogBuffer::FirstTimestamp() const { |
| return messages_.empty() ? std::nullopt : std::make_optional(messages_.front().timestamp); |
| } |
| |
| std::optional<zx::time_boot> LogBuffer::LastTimestamp() const { |
| return messages_.empty() ? std::nullopt : std::make_optional(messages_.back().timestamp); |
| } |
| |
| bool LogBuffer::Add(LogSink::MessageOr message) { |
| if (message.is_ok()) { |
| redactor_->Redact(message.value().msg); |
| for (std::string& tag : message.value().tags) { |
| redactor_->Redact(tag); |
| } |
| } else { |
| redactor_->Redact(message.error()); |
| } |
| |
| // Assume timestamp 0 if no messages have been added yet. |
| const zx::time_boot last_timestamp = |
| (messages_.empty()) ? zx::time_boot(0) : messages_.back().timestamp; |
| const std::string& msg = (message.is_ok()) ? message.value().msg : message.error(); |
| const int32_t& severity = (message.is_ok()) ? message.value().severity : kDefaultLogSeverity; |
| const std::vector<std::string>& tags = (message.is_ok()) ? message.value().tags : kDefaultTags; |
| |
| // Adds a new message to |messages_| and updates internal accounting. |
| auto AddNew = [this, &message, &msg, &severity, &tags, last_timestamp] { |
| messages_.emplace_back(message, last_timestamp); |
| size_ += messages_.back().msg.size(); |
| |
| last_msg_ = msg; |
| last_severity_ = severity; |
| last_tags = tags; |
| last_msg_repeated_ = 0; |
| is_sorted_ &= messages_.back().timestamp >= last_timestamp; |
| |
| return true; |
| }; |
| |
| const zx::time_boot action_timestamp = (message.is_ok()) ? message.value().time : last_timestamp; |
| auto on_return = ::fit::defer([this, action_timestamp] { |
| RunActions(action_timestamp); |
| EnforceCapacity(); |
| }); |
| |
| if (messages_.empty()) { |
| return AddNew(); |
| } |
| |
| // The most recent message is repeated, don't need to create new data. |
| if (last_msg_ == msg && last_severity_ == severity && last_tags == tags) { |
| ++last_msg_repeated_; |
| return true; |
| } |
| |
| // Inject a signal the most previously added message was repeated. |
| if (last_msg_repeated_ > 0) { |
| size_ += AppendRepeated(last_msg_repeated_, messages_.back().msg); |
| } |
| |
| return AddNew(); |
| } |
| |
| void LogBuffer::NotifyInterruption() { |
| messages_.clear(); |
| ResetLastMessage(); |
| is_sorted_ = true; |
| size_ = 0u; |
| |
| // Executing and deleting all remaining actions is safe because non-SystemLog controlled |
| // interruptions aren't expected to occur. |
| for (auto& [_, action] : actions_at_time_) { |
| action(); |
| } |
| actions_at_time_.clear(); |
| } |
| |
| std::string LogBuffer::ToString() { |
| // Ensure messages appear in time order. |
| Sort(); |
| |
| std::string out; |
| out.reserve(size_); |
| for (const Message& message : messages_) { |
| out.append(message.msg); |
| } |
| |
| // Inject a signal the last message was repeated because the signal doesn't exist in the |
| // log yet. |
| if (last_msg_repeated_ > 0) { |
| AppendRepeated(last_msg_repeated_, out); |
| } |
| |
| return out; |
| } |
| |
| void LogBuffer::ExecuteAfter(const zx::time_boot uptime, ::fit::closure action) { |
| actions_at_time_.insert({uptime, std::move(action)}); |
| } |
| |
| void LogBuffer::Sort() { |
| // No sort is needed. |
| if (is_sorted_) { |
| return; |
| } |
| |
| // Inject a signal the last message was repeated because the sort may change which message is |
| // last. |
| if (last_msg_repeated_ > 0) { |
| size_ += AppendRepeated(last_msg_repeated_, messages_.back().msg); |
| } |
| |
| std::stable_sort(messages_.begin(), messages_.end(), [](const Message& lhs, const Message& rhs) { |
| return lhs.timestamp < rhs.timestamp; |
| }); |
| is_sorted_ = true; |
| |
| // Reset the message last added. |
| // |
| // Note: info used to deduplicate messages is lost; it has not yet been proven important enough |
| // in the system log to justify the cost of identifying what the original msg was and |
| // aggregating all adjacent messages that match it. For example, it may be possible to see the |
| // sequence: |
| // |
| // LOG MESSAGE A |
| // !!! MESSAGE REPEATED 3 MORE TIMES!!! |
| // LOG MESSAGE A |
| // |
| // in a final system log. |
| ResetLastMessage(); |
| } |
| |
| void LogBuffer::RunActions(const zx::time_boot timestamp) { |
| for (auto it = actions_at_time_.lower_bound(timestamp); it != actions_at_time_.end();) { |
| it->second(); |
| actions_at_time_.erase(it++); |
| } |
| } |
| |
| void LogBuffer::EnforceCapacity() { |
| if (size_ <= capacity_) { |
| return; |
| } |
| |
| // Ensure messages are dropped in time order. |
| Sort(); |
| while (size_ > capacity_ && !messages_.empty()) { |
| size_ -= messages_.front().msg.size(); |
| messages_.pop_front(); |
| } |
| } |
| |
| void LogBuffer::ResetLastMessage() { |
| last_msg_ = ""; |
| last_severity_ = 0; |
| last_tags = {}; |
| last_msg_repeated_ = 0u; |
| } |
| |
| LogBuffer::Message::Message(const LogSink::MessageOr& message, zx::time_boot default_timestamp) |
| : timestamp(message.is_ok() ? message.value().time : default_timestamp), |
| msg(message.is_ok() ? Format(message.value()) |
| : fxl::StringPrintf("!!! Failed to format chunk: %s !!!\n", |
| message.error().c_str())) {} |
| |
| } // namespace forensics::feedback |