| // Copyright 2021 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 |
| |
| #ifndef COBALT_SRC_LIB_UTIL_STATUS_BUILDER_H_ |
| #define COBALT_SRC_LIB_UTIL_STATUS_BUILDER_H_ |
| |
| #include <string> |
| |
| #include "src/lib/util/source_location.h" |
| #include "src/pb/event.pb.h" |
| #include "src/public/lib/status.h" |
| #include "src/public/lib/status_codes.h" |
| #include "src/registry/metric_definition.pb.h" |
| #include "src/registry/report_definition.pb.h" |
| |
| namespace cobalt::util { |
| |
| // virtual base ContextFormatter, that will be specialized for various types. |
| // If there is no specialized implementation for a given type, a custom formatter isn't present, and |
| // can't be used as an argument to WithContexts. |
| template <typename T> |
| class ContextFormatter { |
| virtual ~ContextFormatter() = default; |
| |
| // Return the default key to use if passed to WithContexts (should be inline and static if |
| // possible) |
| virtual std::string default_key() = 0; |
| |
| // Write the value to the given ostream (should be inline) |
| virtual void write_value(std::ostream& stream) = 0; |
| }; |
| |
| class [[nodiscard]] StatusBuilder { |
| public: |
| explicit StatusBuilder() = delete; |
| ~StatusBuilder() = default; |
| |
| // Create a StatusBuilder based on |original_status|. Used to add extra contextual information to |
| // the status. |
| explicit StatusBuilder(const Status& original_status, |
| util::SourceLocation location = util::SourceLocation::current()); |
| |
| // Create a StatusBuilder for a given |status_code|. |
| explicit StatusBuilder(StatusCode status_code, |
| util::SourceLocation location = util::SourceLocation::current()); |
| |
| // Create a StatusBuilder for a given |status_code| with an |initial message|. |
| StatusBuilder(StatusCode status_code, const std::string& initial_message, |
| util::SourceLocation location = util::SourceLocation::current()); |
| |
| // Make StatusBuilder move-only. |
| StatusBuilder(const StatusBuilder& sb) = delete; |
| StatusBuilder& operator=(const StatusBuilder& sb) = delete; |
| StatusBuilder(StatusBuilder&&) = default; |
| StatusBuilder& operator=(StatusBuilder&&) = default; |
| |
| // Converts the |StatusBuilder| into a |Status| object. |
| Status Build(); |
| |
| StatusBuilder& SetCode(StatusCode status_code) & { |
| code_ = status_code; |
| return *this; |
| } |
| // "Owned" (rvalue) version of SetCode that doesn}'t convert StatusBuilder into a reference |
| // (lvalue) |
| [[nodiscard]] StatusBuilder&& SetCode(StatusCode status_code) && { |
| return std::move(SetCode(status_code)); |
| } |
| |
| // LogLevel is used to denote where the status should be logged on construction. |
| enum class LogLevel { |
| INFO, |
| WARNING, |
| ERROR, |
| NO_LOGGING, |
| }; |
| |
| // These methods define where the status should be logged on construction. If |
| // none of these methods are called, or |LogLevel::NO_LOGGING| is provided to |
| // the |Log| method, nothing will be logged when the |StatusBuilder| is |
| // converted into a |Status|. |
| StatusBuilder& Log(LogLevel level) & { |
| log_level_ = level; |
| return *this; |
| } |
| // "Owned" (rvalue) version of Log that does not convert StatusBuilder to a reference (lvalue) |
| [[nodiscard]] StatusBuilder&& Log(LogLevel level) && { return std::move(Log(level)); } |
| |
| // When the |Status| object is created, send it to LOG(ERROR). |
| StatusBuilder& LogError() & { return Log(LogLevel::ERROR); } |
| // "Owned" (rvalue) version of LogError that does not convert StatusBuilder to a reference |
| // (lvalue) |
| [[nodiscard]] StatusBuilder&& LogError() && { return std::move(LogError()); } |
| |
| // When the |Status| object is created, send it to LOG(WARNING) |
| StatusBuilder& LogWarning() & { return Log(LogLevel::WARNING); } |
| // "Owned" (rvalue) version of LogWarning that does not convert StatusBuilder to a reference |
| // (lvalue) |
| [[nodiscard]] StatusBuilder&& LogWarning() && { return std::move(LogWarning()); } |
| |
| // When the |Status| object is created, send it to LOG(INFO) |
| StatusBuilder& LogInfo() & { return Log(LogLevel::INFO); } |
| // "Owned" (rvalue) version of LogInfo that does not convert StatusBuilder to a reference (lvalue) |
| [[nodiscard]] StatusBuilder&& LogInfo() && { return std::move(LogInfo()); } |
| |
| // WithContext can be used to easily add contextual information to a Status |
| // while maintaining the error_code, and error_message. |
| // |
| // Example: |
| // Status result = DoSomething(); |
| // if (!status.ok()) { |
| // // This will add 'Metric=<metric_id>' to the error_details string. |
| // return util::StatusBuilder(result).WithContext("Metric", metric_id); |
| // } |
| template <typename V> |
| inline StatusBuilder& WithContext(const std::string& key, const V& value) & { |
| // Check if stream is empty. |
| if (error_details_.tellp() != std::streampos(0)) { |
| error_details_ << ", "; |
| } |
| error_details_ << key << "="; |
| if constexpr (std::is_constructible<ContextFormatter<V>, V>::value) { |
| // Use the custom ContextFormatter for the value. |
| ContextFormatter<V>(value).write_value(error_details_); |
| } else { |
| error_details_ << value; |
| } |
| return *this; |
| } |
| // "Owned" (rvalue) version of WithContext that doesn't convert the StatusBuilder into a reference |
| // (lvalue). |
| template <typename V> |
| inline StatusBuilder&& WithContext(const std::string& key, const V& value) && { |
| return std::move(WithContext(key, value)); |
| } |
| |
| // Single argument version of WithContext for types where there is a ContextFormatter defined. |
| template <typename V> |
| inline StatusBuilder& WithContexts(const V& v) & { |
| static_assert(std::is_constructible<ContextFormatter<V>, V>::value, |
| "All arguments to StatusBuilder::WithContext *must* have a ContextFormatter " |
| "implementation"); |
| return WithContext(ContextFormatter<V>(v).default_key(), v); |
| } |
| // "Owned" (rvalue) version of the above method that doesn't convert StatusBuilder into reference |
| // (lvalue). |
| template <typename V> |
| inline StatusBuilder&& WithContexts(const V& v) && { |
| return std::move(WithContexts(v)); |
| } |
| |
| // Variadic version of WithContexts. |
| template <typename V, typename... ContextValues> |
| inline StatusBuilder& WithContexts(const V& v, ContextValues&&... args) & { |
| WithContexts(v); |
| return WithContexts(std::forward<ContextValues>(args)...); |
| } |
| // "Owned" (rvalue) version of the above method that doesn't convert StatusBuilder into reference |
| // (lvalue). |
| template <typename V, typename... ContextValues> |
| inline StatusBuilder&& WithContexts(const V& v, ContextValues&&... args) && { |
| return std::move(WithContexts(v, std::forward<ContextValues>(args)...)); |
| } |
| |
| // AppendMsg appends the value to the error_message; |
| template <typename V> |
| inline StatusBuilder& AppendMsg(const V& value) & { |
| if constexpr (std::is_constructible<ContextFormatter<V>, V>::value) { |
| // Use the custom ContextFormatter for the value. |
| ContextFormatter<V>(value).write_value(error_message_); |
| } else { |
| error_message_ << value; |
| } |
| return *this; |
| } |
| |
| // "Owned" (rvalue) version of AppendMsg that doesn't convert the StatusBuilder into a reference |
| // (lvalue). |
| template <typename V> |
| [[nodiscard]] inline StatusBuilder&& AppendMsg(const V& value) && { |
| return std::move(AppendMsg(value)); |
| } |
| |
| private: |
| util::SourceLocation location_; |
| StatusCode code_ = StatusCode::OK; |
| LogLevel log_level_ = LogLevel::NO_LOGGING; |
| std::stringstream error_message_; |
| std::stringstream error_details_; |
| }; |
| |
| // Specialization of ContextFormatter for cobalt::ReportDefinition to give it default formatting and |
| // allow it to be passed to StatusBuilder::WithContexts. |
| // |
| // Will print Report=<report_name()> by default. |
| template <> |
| class ContextFormatter<ReportDefinition> { |
| public: |
| explicit ContextFormatter(const ReportDefinition& v) : v_(v) {} |
| inline static std::string default_key() { return "Report"; } |
| inline void write_value(std::ostream& stream) { stream << v_.report_name(); } |
| |
| private: |
| const ReportDefinition& v_; |
| }; |
| |
| // Specialization of ContextFormatter for cobalt::MetricDefinition to give it default formatting and |
| // allow it to be passed to StatusBuilder::WithContexts. |
| // |
| // Will print Metric=<metric_name()> by default. |
| template <> |
| class ContextFormatter<MetricDefinition> { |
| public: |
| explicit ContextFormatter(const MetricDefinition& v) : v_(v) {} |
| inline static std::string default_key() { return "Metric"; } |
| inline void write_value(std::ostream& stream) { stream << v_.metric_name(); } |
| |
| private: |
| const MetricDefinition& v_; |
| }; |
| |
| // Specialization of ContextFormatter for protobuf enums to give them default formatting and allow |
| // it to be passed to StatusBuilder::WithContexts. |
| // |
| // Will print <enum_type>=<enum_string_value> by default. |
| #define PB_ENUM_FORMATTER(ty) \ |
| template <> \ |
| class ContextFormatter<ty> { \ |
| public: \ |
| explicit ContextFormatter(const ty& v) : v_(v) {} \ |
| inline static std::string default_key() { return #ty; } \ |
| inline void write_value(std::ostream& stream) { stream << ty##_Name(v_); } \ |
| \ |
| private: \ |
| const ty& v_; \ |
| } |
| |
| PB_ENUM_FORMATTER(MetricDefinition::MetricType); |
| PB_ENUM_FORMATTER(ReportDefinition::OnDeviceAggregationType); |
| PB_ENUM_FORMATTER(ReportDefinition::ReportType); |
| |
| #undef PB_ENUM_FORMATTER |
| |
| // Specialization of ContextFormatter for Event::TypeCase to give it default formatting and allow it |
| // to be passed to StatusBuilder::WithContexts. |
| // |
| // Will print Event::TypeCase=<v()> by default. Where v() maps to a human-readable string. |
| template <> |
| class ContextFormatter<Event::TypeCase> { |
| public: |
| explicit ContextFormatter(const Event::TypeCase& v) : v_(v) {} |
| inline static std::string default_key() { return "Event::TypeCase"; } |
| std::string v() { |
| switch (v_) { |
| case Event::kEventOccurredEvent: |
| return "EVENT_OCCURRED"; |
| case Event::kEventCountEvent: |
| return "EVENT_COUNT"; |
| case Event::kElapsedTimeEvent: |
| return "ELAPSED_TIME"; |
| case Event::kFrameRateEvent: |
| return "FRAME_RATE"; |
| case Event::kMemoryUsageEvent: |
| return "MEMORY_USAGE"; |
| case Event::kIntHistogramEvent: |
| return "INT_HISTOGRAM"; |
| case Event::kOccurrenceEvent: |
| return "OCCURRENCE"; |
| case Event::kIntegerEvent: |
| return "INTEGER"; |
| case Event::kIntegerHistogramEvent: |
| return "INTEGER_HISTOGRAM"; |
| case Event::kStringEvent: |
| return "STRING"; |
| case Event::kCustomEvent: |
| return "CUSTOM"; |
| default: |
| return "UNKNOWN_CASE"; |
| } |
| } |
| inline void write_value(std::ostream& stream) { stream << v(); } |
| |
| private: |
| const Event::TypeCase& v_; |
| }; |
| |
| } // namespace cobalt::util |
| |
| #endif // COBALT_SRC_LIB_UTIL_STATUS_BUILDER_H_ |