summaryrefslogtreecommitdiffstats
path: root/library/cpp/yt/error/error.cpp
diff options
context:
space:
mode:
authorarkady-e1ppa <[email protected]>2024-12-25 07:12:23 +0300
committerarkady-e1ppa <[email protected]>2024-12-25 07:31:55 +0300
commitb5dd91799751f9924acb7c17ddad16ddb2086bba (patch)
treef430b4e9f752af7411d5503bfcf823e9718056c2 /library/cpp/yt/error/error.cpp
parent75f1af270a6cf9a17b65fde6d12efbb94f235960 (diff)
YT-21233: Adjust (To/From)ErrorAttributeValue CPOs, revert ConvertTo becoming CPO, move TErrorCode and TError to library/cpp/yt/error
\[nodiff:caesar\] List of changes: 1) Make `ConvertTo` a normal function again (basically, a revert of previous change). It turned out to be very inconvenient entry point 2) Introduce concept for "Primitive types" which are handled without yt/core module. Please note that inclusion of core **does not** affect work with primitive types (otherwise we would be facing and ODR violation). These are numerics, duration, instant, string-like objects and guid 3) Introduce `FromErrorAttributeValue` which is a substitute to `ConvertTo` with special behavior (see below) 4) Scheme for (de-)serialization has changed: 1) Primitive types are handled via `ConvertToTextYsonString` and `ConvertFromTextYsonString` 2) Other types are not supported without yt/core. Otherwise `ConvertToYsonString(value, EYsonFormat::Text)` used for serialization and `ConvertTo<T>(value)` for deserialization. 5) New format of attribute value storage allows to not care about concrete type of the attribute (text yson strings can be detected since they start with `\"` and deserialized so are text yson bools, everything else is already human readable). This drops dependency on `TTokenizer` 6) Some small parts (e.g. enums or constants or aliases) of yt/core/misc files were moved to library/cpp/yt/\* locations and re-included to drop dependencies of stripped\_error on them 7) `TErrorAttributes` is now a simple hash map instead of a handle to `IAttributeDictionary` 8) `ExtractFromAttributes` weak symbol is finally moved to library/cpp/yt/error due to point 9 allowing to fully drop dependency on `IAttributeDictionary`. Combined with point 7 this drops dep on yt/core/ytree altogether 9) Moved `TErrorCode` to library/cpp/yt/error. There is a logger there which forces dep on library/cpp/yt/logging. It is not required really, and can be later removed 10) Moved `TError` with format and macroes to library/cpp/yt/error. It still (and probably forever will) depend on yson. What can be done next: Switch TYsonString to TString and move ConvertTo/FromTextYsonString to library/cpp/yt/error. This would remove dep on library/cpp/yt/yson\_string commit_hash:6f11dc478ab782a1f98a5aedcd45a4800d3f4e7b
Diffstat (limited to 'library/cpp/yt/error/error.cpp')
-rw-r--r--library/cpp/yt/error/error.cpp827
1 files changed, 827 insertions, 0 deletions
diff --git a/library/cpp/yt/error/error.cpp b/library/cpp/yt/error/error.cpp
new file mode 100644
index 00000000000..bcc265ab2ed
--- /dev/null
+++ b/library/cpp/yt/error/error.cpp
@@ -0,0 +1,827 @@
+#include "error.h"
+
+#include <library/cpp/yt/exception/exception.h>
+
+#include <library/cpp/yt/error/error_attributes.h>
+#include <library/cpp/yt/error/origin_attributes.h>
+
+#include <library/cpp/yt/string/string.h>
+
+#include <library/cpp/yt/system/proc.h>
+
+#include <library/cpp/yt/yson_string/string.h>
+
+#include <util/string/subst.h>
+
+#include <util/system/error.h>
+#include <util/system/thread.h>
+
+namespace NYT {
+
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, TErrorCode code, TStringBuf spec)
+{
+ FormatValue(builder, static_cast<int>(code), spec);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr TStringBuf ErrorMessageTruncatedSuffix = "...<message truncated>";
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TError::TImpl
+{
+public:
+ TImpl()
+ : Code_(NYT::EErrorCode::OK)
+ { }
+
+ TImpl(const TError::TImpl& other)
+ : Code_(other.Code_)
+ , Message_(other.Message_)
+ , OriginAttributes_(other.OriginAttributes_)
+ , Attributes_(other.Attributes_)
+ , InnerErrors_(other.InnerErrors_)
+ { }
+
+ explicit TImpl(TString message)
+ : Code_(NYT::EErrorCode::Generic)
+ , Message_(std::move(message))
+ {
+ OriginAttributes_.Capture();
+ }
+
+ TImpl(TErrorCode code, TString message)
+ : Code_(code)
+ , Message_(std::move(message))
+ {
+ if (!IsOK()) {
+ OriginAttributes_.Capture();
+ }
+ }
+
+ bool IsOK() const noexcept
+ {
+ return Code_ == NYT::EErrorCode::OK;
+ }
+
+ TErrorCode GetCode() const noexcept
+ {
+ return Code_;
+ }
+
+ void SetCode(TErrorCode code) noexcept
+ {
+ Code_ = code;
+ }
+
+ const TString& GetMessage() const noexcept
+ {
+ return Message_;
+ }
+
+ TString* MutableMessage() noexcept
+ {
+ return &Message_;
+ }
+
+ bool HasOriginAttributes() const noexcept
+ {
+ return OriginAttributes_.ThreadName.Length > 0;
+ }
+
+ const TOriginAttributes& OriginAttributes() const noexcept
+ {
+ return OriginAttributes_;
+ }
+
+ TOriginAttributes* MutableOriginAttributes() noexcept
+ {
+ return &OriginAttributes_;
+ }
+
+ TProcessId GetPid() const noexcept
+ {
+ return OriginAttributes_.Pid;
+ }
+
+ NThreading::TThreadId GetTid() const noexcept
+ {
+ return OriginAttributes_.Tid;
+ }
+
+ TStringBuf GetThreadName() const noexcept
+ {
+ return OriginAttributes_.ThreadName.ToStringBuf();
+ }
+
+ bool HasDatetime() const noexcept
+ {
+ return OriginAttributes_.Datetime != TInstant();
+ }
+
+ TInstant GetDatetime() const noexcept
+ {
+ return OriginAttributes_.Datetime;
+ }
+
+ void SetDatetime(TInstant datetime) noexcept
+ {
+ OriginAttributes_.Datetime = datetime;
+ }
+
+ bool HasAttributes() const noexcept
+ {
+ return true;
+ }
+
+ const TErrorAttributes& Attributes() const
+ {
+ return Attributes_;
+ }
+
+ void UpdateOriginAttributes()
+ {
+ OriginAttributes_ = NYT::NDetail::ExtractFromDictionary(&Attributes_);
+ }
+
+ TErrorAttributes* MutableAttributes() noexcept
+ {
+ return &Attributes_;
+ }
+
+ const std::vector<TError>& InnerErrors() const noexcept
+ {
+ return InnerErrors_;
+ }
+
+ std::vector<TError>* MutableInnerErrors() noexcept
+ {
+ return &InnerErrors_;
+ }
+
+private:
+ TErrorCode Code_;
+ TString Message_;
+
+ TOriginAttributes OriginAttributes_{
+ .Pid = 0,
+ .Tid = NThreading::InvalidThreadId,
+ };
+
+ TErrorAttributes Attributes_;
+
+ std::vector<TError> InnerErrors_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+bool IsWhitelisted(const TError& error, const THashSet<TStringBuf>& attributeWhitelist)
+{
+ for (const auto& key : error.Attributes().ListKeys()) {
+ if (attributeWhitelist.contains(key)) {
+ return true;
+ }
+ }
+
+ for (const auto& innerError : error.InnerErrors()) {
+ if (IsWhitelisted(innerError, attributeWhitelist)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//! Returns vector which consists of objects from errors such that:
+//! if N is the number of objects in errors s.t. IsWhitelisted is true
+//! then first N objects of returned vector are the ones for which IsWhitelisted is true
+//! followed by std::max(0, maxInnerErrorCount - N - 1) remaining objects
+//! finally followed by errors.back().
+std::vector<TError>& ApplyWhitelist(std::vector<TError>& errors, const THashSet<TStringBuf>& attributeWhitelist, int maxInnerErrorCount)
+{
+ if (std::ssize(errors) < std::max(2, maxInnerErrorCount)) {
+ return errors;
+ }
+
+ auto firstNotWhitelisted = std::partition(
+ errors.begin(),
+ std::prev(errors.end()),
+ [&attributeWhitelist] (const TError& error) {
+ return IsWhitelisted(error, attributeWhitelist);
+ });
+
+ int lastErrorOffset = std::max<int>(firstNotWhitelisted - errors.begin(), maxInnerErrorCount - 1);
+
+ *(errors.begin() + lastErrorOffset) = std::move(errors.back());
+ errors.resize(lastErrorOffset + 1);
+
+ return errors;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TError::TErrorOr() = default;
+
+TError::~TErrorOr() = default;
+
+TError::TErrorOr(const TError& other)
+{
+ if (!other.IsOK()) {
+ Impl_ = std::make_unique<TImpl>(*other.Impl_);
+ }
+}
+
+TError::TErrorOr(TError&& other) noexcept
+ : Impl_(std::move(other.Impl_))
+{ }
+
+TError::TErrorOr(const std::exception& ex)
+{
+ if (auto simpleException = dynamic_cast<const TSimpleException*>(&ex)) {
+ *this = TError(NYT::EErrorCode::Generic, TRuntimeFormat{simpleException->GetMessage()});
+ // NB: clang-14 is incapable of capturing structured binding variables
+ // so we force materialize them via this function call.
+ auto addAttribute = [this] (const auto& key, const auto& value) {
+ std::visit([&] (const auto& actual) {
+ *this <<= TErrorAttribute(key, actual);
+ }, value);
+ };
+ for (const auto& [key, value] : simpleException->GetAttributes()) {
+ addAttribute(key, value);
+ }
+ try {
+ if (simpleException->GetInnerException()) {
+ std::rethrow_exception(simpleException->GetInnerException());
+ }
+ } catch (const std::exception& innerEx) {
+ *this <<= TError(innerEx);
+ }
+ } else if (const auto* errorEx = dynamic_cast<const TErrorException*>(&ex)) {
+ *this = errorEx->Error();
+ } else {
+ *this = TError(NYT::EErrorCode::Generic, TRuntimeFormat{ex.what()});
+ }
+ YT_VERIFY(!IsOK());
+}
+
+TError::TErrorOr(TString message, TDisableFormat)
+ : Impl_(std::make_unique<TImpl>(std::move(message)))
+{ }
+
+TError::TErrorOr(TErrorCode code, TString message, TDisableFormat)
+ : Impl_(std::make_unique<TImpl>(code, std::move(message)))
+{ }
+
+TError& TError::operator = (const TError& other)
+{
+ if (other.IsOK()) {
+ Impl_.reset();
+ } else {
+ Impl_ = std::make_unique<TImpl>(*other.Impl_);
+ }
+ return *this;
+}
+
+TError& TError::operator = (TError&& other) noexcept
+{
+ Impl_ = std::move(other.Impl_);
+ return *this;
+}
+
+TError TError::FromSystem()
+{
+ return FromSystem(LastSystemError());
+}
+
+TError TError::FromSystem(int error)
+{
+ return TError(TErrorCode(LinuxErrorCodeBase + error), TRuntimeFormat{LastSystemErrorText(error)}) <<
+ TErrorAttribute("errno", error);
+}
+
+TError TError::FromSystem(const TSystemError& error)
+{
+ return FromSystem(error.Status());
+}
+
+TErrorCode TError::GetCode() const
+{
+ if (!Impl_) {
+ return NYT::EErrorCode::OK;
+ }
+ return Impl_->GetCode();
+}
+
+TError& TError::SetCode(TErrorCode code)
+{
+ MakeMutable();
+ Impl_->SetCode(code);
+ return *this;
+}
+
+TErrorCode TError::GetNonTrivialCode() const
+{
+ if (!Impl_) {
+ return NYT::EErrorCode::OK;
+ }
+
+ if (GetCode() != NYT::EErrorCode::Generic) {
+ return GetCode();
+ }
+
+ for (const auto& innerError : InnerErrors()) {
+ auto innerCode = innerError.GetNonTrivialCode();
+ if (innerCode != NYT::EErrorCode::Generic) {
+ return innerCode;
+ }
+ }
+
+ return GetCode();
+}
+
+THashSet<TErrorCode> TError::GetDistinctNonTrivialErrorCodes() const
+{
+ THashSet<TErrorCode> result;
+ TraverseError(*this, [&result] (const TError& error, int /*depth*/) {
+ if (auto errorCode = error.GetCode(); errorCode != NYT::EErrorCode::OK) {
+ result.insert(errorCode);
+ }
+ });
+ return result;
+}
+
+const TString& TError::GetMessage() const
+{
+ if (!Impl_) {
+ static const TString Result;
+ return Result;
+ }
+ return Impl_->GetMessage();
+}
+
+TError& TError::SetMessage(TString message)
+{
+ MakeMutable();
+ *Impl_->MutableMessage() = std::move(message);
+ return *this;
+}
+
+bool TError::HasOriginAttributes() const
+{
+ if (!Impl_) {
+ return false;
+ }
+ return Impl_->HasOriginAttributes();
+}
+
+bool TError::HasDatetime() const
+{
+ if (!Impl_) {
+ return false;
+ }
+ return Impl_->HasDatetime();
+}
+
+TInstant TError::GetDatetime() const
+{
+ if (!Impl_) {
+ return {};
+ }
+ return Impl_->GetDatetime();
+}
+
+TProcessId TError::GetPid() const
+{
+ if (!Impl_) {
+ return 0;
+ }
+ return Impl_->GetPid();
+}
+
+NThreading::TThreadId TError::GetTid() const
+{
+ if (!Impl_) {
+ return NThreading::InvalidThreadId;
+ }
+ return Impl_->GetTid();
+}
+
+TStringBuf TError::GetThreadName() const
+{
+ if (!Impl_) {
+ static TString empty;
+ return empty;
+ }
+ return Impl_->GetThreadName();
+}
+
+bool TError::HasAttributes() const noexcept
+{
+ if (!Impl_) {
+ return false;
+ }
+ return Impl_->HasAttributes();
+}
+
+const TErrorAttributes& TError::Attributes() const
+{
+ if (!Impl_) {
+ static TErrorAttributes empty = {};
+ return empty;
+ }
+ return Impl_->Attributes();
+}
+
+TErrorAttributes* TError::MutableAttributes()
+{
+ MakeMutable();
+ return Impl_->MutableAttributes();
+}
+
+const std::vector<TError>& TError::InnerErrors() const
+{
+ if (!Impl_) {
+ static const std::vector<TError> Result;
+ return Result;
+ }
+ return Impl_->InnerErrors();
+}
+
+std::vector<TError>* TError::MutableInnerErrors()
+{
+ MakeMutable();
+ return Impl_->MutableInnerErrors();
+}
+
+TOriginAttributes* TError::MutableOriginAttributes() const noexcept
+{
+ if (!Impl_) {
+ return nullptr;
+ }
+ return Impl_->MutableOriginAttributes();
+}
+
+void TError::UpdateOriginAttributes()
+{
+ if (!Impl_) {
+ return;
+ }
+ Impl_->UpdateOriginAttributes();
+}
+
+const TString InnerErrorsTruncatedKey("inner_errors_truncated");
+
+TError TError::Truncate(
+ int maxInnerErrorCount,
+ i64 stringLimit,
+ const THashSet<TStringBuf>& attributeWhitelist) const &
+{
+ if (!Impl_) {
+ return TError();
+ }
+
+ auto truncateInnerError = [=, &attributeWhitelist] (const TError& innerError) {
+ return innerError.Truncate(maxInnerErrorCount, stringLimit, attributeWhitelist);
+ };
+
+ auto truncateAttributes = [stringLimit, &attributeWhitelist] (const TErrorAttributes& attributes, TErrorAttributes* mutableAttributes) {
+ for (const auto& key : attributes.ListKeys()) {
+ const auto& value = attributes.FindValue(key);
+
+ if (std::ssize(value.AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) {
+ mutableAttributes->SetValue(
+ key,
+ NYT::ToErrorAttributeValue("...<attribute truncated>..."));
+ } else {
+ mutableAttributes->SetValue(
+ key,
+ value);
+ }
+ }
+ };
+
+ auto result = std::make_unique<TImpl>();
+ result->SetCode(GetCode());
+ *result->MutableMessage()
+ = std::move(TruncateString(GetMessage(), stringLimit, ErrorMessageTruncatedSuffix));
+ if (Impl_->HasAttributes()) {
+ truncateAttributes(Impl_->Attributes(), result->MutableAttributes());
+ }
+ *result->MutableOriginAttributes() = Impl_->OriginAttributes();
+
+ const auto& innerErrors = InnerErrors();
+ auto& copiedInnerErrors = *result->MutableInnerErrors();
+
+ if (std::ssize(innerErrors) <= maxInnerErrorCount) {
+ for (const auto& innerError : innerErrors) {
+ copiedInnerErrors.push_back(truncateInnerError(innerError));
+ }
+ } else {
+ result->MutableAttributes()->SetValue(InnerErrorsTruncatedKey, NYT::ToErrorAttributeValue(true));
+
+ // NB(arkady-e1ppa): We want to always keep the last inner error,
+ // so we make room for it and do not check if it is whitelisted.
+ for (int idx = 0; idx < std::ssize(innerErrors) - 1; ++idx) {
+ const auto& innerError = innerErrors[idx];
+ if (
+ IsWhitelisted(innerError, attributeWhitelist) ||
+ std::ssize(copiedInnerErrors) < maxInnerErrorCount - 1)
+ {
+ copiedInnerErrors.push_back(truncateInnerError(innerError));
+ }
+ }
+ copiedInnerErrors.push_back(truncateInnerError(innerErrors.back()));
+ }
+
+ return TError(std::move(result));
+}
+
+TError TError::Truncate(
+ int maxInnerErrorCount,
+ i64 stringLimit,
+ const THashSet<TStringBuf>& attributeWhitelist) &&
+{
+ if (!Impl_) {
+ return TError();
+ }
+
+ auto truncateInnerError = [=, &attributeWhitelist] (TError& innerError) {
+ innerError = std::move(innerError).Truncate(maxInnerErrorCount, stringLimit, attributeWhitelist);
+ };
+
+ auto truncateAttributes = [stringLimit, &attributeWhitelist] (TErrorAttributes* attributes) {
+ for (const auto& key : attributes->ListKeys()) {
+ if (std::ssize(attributes->FindValue(key).AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) {
+ attributes->SetValue(
+ key,
+ NYT::ToErrorAttributeValue("...<attribute truncated>..."));
+ }
+ }
+ };
+
+ TruncateStringInplace(Impl_->MutableMessage(), stringLimit, ErrorMessageTruncatedSuffix);
+ if (Impl_->HasAttributes()) {
+ truncateAttributes(Impl_->MutableAttributes());
+ }
+ if (std::ssize(InnerErrors()) <= maxInnerErrorCount) {
+ for (auto& innerError : *MutableInnerErrors()) {
+ truncateInnerError(innerError);
+ }
+ } else {
+ auto& innerErrors = ApplyWhitelist(*MutableInnerErrors(), attributeWhitelist, maxInnerErrorCount);
+ MutableAttributes()->SetValue(InnerErrorsTruncatedKey, NYT::ToErrorAttributeValue(true));
+
+ for (auto& innerError : innerErrors) {
+ truncateInnerError(innerError);
+ }
+ }
+
+ return std::move(*this);
+}
+
+bool TError::IsOK() const
+{
+ if (!Impl_) {
+ return true;
+ }
+ return Impl_->IsOK();
+}
+
+TError TError::Wrap() const &
+{
+ return *this;
+}
+
+TError TError::Wrap() &&
+{
+ return std::move(*this);
+}
+
+Y_WEAK TString GetErrorSkeleton(const TError& /*error*/)
+{
+ // Proper implementation resides in yt/yt/library/error_skeleton/skeleton.cpp.
+ THROW_ERROR_EXCEPTION("Error skeleton implementation library is not linked; consider PEERDIR'ing yt/yt/library/error_skeleton");
+}
+
+TString TError::GetSkeleton() const
+{
+ return GetErrorSkeleton(*this);
+}
+
+std::optional<TError> TError::FindMatching(TErrorCode code) const
+{
+ return FindMatching([&] (TErrorCode errorCode) {
+ return code == errorCode;
+ });
+}
+
+std::optional<TError> TError::FindMatching(const THashSet<TErrorCode>& codes) const
+{
+ return FindMatching([&] (TErrorCode code) {
+ return codes.contains(code);
+ });
+}
+
+TError::TErrorOr(std::unique_ptr<TImpl> impl)
+ : Impl_(std::move(impl))
+{ }
+
+void TError::MakeMutable()
+{
+ if (!Impl_) {
+ Impl_ = std::make_unique<TImpl>();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TError& TError::operator <<= (const TErrorAttribute& attribute) &
+{
+ MutableAttributes()->SetAttribute(attribute);
+ return *this;
+}
+
+TError& TError::operator <<= (const std::vector<TErrorAttribute>& attributes) &
+{
+ for (const auto& attribute : attributes) {
+ MutableAttributes()->SetAttribute(attribute);
+ }
+ return *this;
+}
+
+TError& TError::operator <<= (const TError& innerError) &
+{
+ MutableInnerErrors()->push_back(innerError);
+ return *this;
+}
+
+TError& TError::operator <<= (TError&& innerError) &
+{
+ MutableInnerErrors()->push_back(std::move(innerError));
+ return *this;
+}
+
+TError& TError::operator <<= (const std::vector<TError>& innerErrors) &
+{
+ MutableInnerErrors()->insert(
+ MutableInnerErrors()->end(),
+ innerErrors.begin(),
+ innerErrors.end());
+ return *this;
+}
+
+TError& TError::operator <<= (std::vector<TError>&& innerErrors) &
+{
+ MutableInnerErrors()->insert(
+ MutableInnerErrors()->end(),
+ std::make_move_iterator(innerErrors.begin()),
+ std::make_move_iterator(innerErrors.end()));
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator == (const TError& lhs, const TError& rhs)
+{
+ if (!lhs.MutableOriginAttributes() && !rhs.MutableOriginAttributes()) {
+ return true;
+ }
+ // NB(arkady-e1ppa): Origin attributes equality comparison is
+ // bit-wise but garbage bits are zeroed so it shouldn't matter.
+ return
+ lhs.GetCode() == rhs.GetCode() &&
+ lhs.GetMessage() == rhs.GetMessage() &&
+ *lhs.MutableOriginAttributes() == *rhs.MutableOriginAttributes() &&
+ lhs.Attributes() == rhs.Attributes() &&
+ lhs.InnerErrors() == rhs.InnerErrors();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void AppendIndent(TStringBuilderBase* builer, int indent)
+{
+ builer->AppendChar(' ', indent);
+}
+
+void AppendAttribute(TStringBuilderBase* builder, const TString& key, const TString& value, int indent)
+{
+ AppendIndent(builder, indent + 4);
+ if (!value.Contains('\n')) {
+ builder->AppendFormat("%-15s %s", key, value);
+ } else {
+ builder->AppendString(key);
+ TString indentedValue = "\n" + value;
+ SubstGlobal(indentedValue, "\n", "\n" + TString{static_cast<size_t>(indent + 8), ' '});
+ // Now first line in indentedValue is empty and every other line is indented by 8 spaces.
+ builder->AppendString(indentedValue);
+ }
+ builder->AppendChar('\n');
+}
+
+void AppendError(TStringBuilderBase* builder, const TError& error, int indent)
+{
+ auto isStringTextYson = [] (const NYson::TYsonString& str) {
+ return
+ str &&
+ std::ssize(str.AsStringBuf()) != 0 &&
+ str.AsStringBuf().front() == '\"';
+ };
+ auto isBoolTextYson = [] (const NYson::TYsonString& str) {
+ return
+ str.AsStringBuf() == "%false" ||
+ str.AsStringBuf() == "%true";
+ };
+
+ if (error.IsOK()) {
+ builder->AppendString("OK");
+ return;
+ }
+
+ AppendIndent(builder, indent);
+ builder->AppendString(error.GetMessage());
+ builder->AppendChar('\n');
+
+ if (error.GetCode() != NYT::EErrorCode::Generic) {
+ AppendAttribute(builder, "code", NYT::ToString(static_cast<int>(error.GetCode())), indent);
+ }
+
+ // Pretty-print origin.
+ const auto* originAttributes = error.MutableOriginAttributes();
+ YT_ASSERT(originAttributes);
+ if (error.HasOriginAttributes()) {
+ AppendAttribute(
+ builder,
+ "origin",
+ NYT::NDetail::FormatOrigin(*originAttributes),
+ indent);
+ } else if (IsErrorSanitizerEnabled() && originAttributes->Host.operator bool()) {
+ AppendAttribute(
+ builder,
+ "host",
+ TString{originAttributes->Host},
+ indent);
+ }
+
+ if (error.HasDatetime()) {
+ AppendAttribute(
+ builder,
+ "datetime",
+ Format("%v", error.GetDatetime()),
+ indent);
+ }
+
+ for (const auto& [key, value] : error.Attributes().ListPairs()) {
+ if (isStringTextYson(value)) {
+ AppendAttribute(builder, key, ConvertFromTextYsonString<TString>(value), indent);
+ } else if (isBoolTextYson(value)) {
+ AppendAttribute(builder, key, TString{FormatBool(ConvertFromTextYsonString<bool>(value))}, indent);
+ } else {
+ AppendAttribute(builder, key, value.ToString(), indent);
+ }
+ }
+
+ for (const auto& innerError : error.InnerErrors()) {
+ builder->AppendChar('\n');
+ AppendError(builder, innerError, indent + 2);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TError& error, TStringBuf /*spec*/)
+{
+ AppendError(builder, error, 0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TraverseError(const TError& error, const TErrorVisitor& visitor, int depth)
+{
+ visitor(error, depth);
+ for (const auto& inner : error.InnerErrors()) {
+ TraverseError(inner, visitor, depth + 1);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const char* TErrorException::what() const noexcept
+{
+ if (CachedWhat_.empty()) {
+ CachedWhat_ = ToString(Error_);
+ }
+ return CachedWhat_.data();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT