tommwq.work/aip

AIP-193 错误

· [tommwq@126.com]
编号 193
原文链接 https://google.aip.dev/193
状态 批准
创建日期 2019-07-26
更新日期 2019-07-26

有效的错误沟通是设计简单直观API的重要组成部分。返回标准错误应答的服务,让API客户端可以构建集中式通用错误处理逻辑。通用逻辑简化了API客户端应用程序,消除了繁琐的零散错误处理代码的需求。

指南

服务 必须 在发生API错误时返回google.rpc.Status消息,*必须* 使用google.rpc.Code中定义的规范错误代码。关于特定代码的更多信息,请参考gRPC状态代码文档

错误消息 应当 帮助普通技术用户 理解解决 问题, 不得 假设用户是特定API的专家。此外,错误消息 不得 假设用户了解全部底层实现细节。

错误消息 应当 简洁可行。额外信息 应当 通过 details 域提供。如果需要提供更多信息, 应当 给出链接,读者可以通过链接获取更多信息或提出问题,以帮助解决问题。在编写文字时,选择适当语气也很重要。

以下部分描述了 google.rpc.Status 域。

Status.message

message 域是面向开发者的、容易阅读的“调试消息”, 应当 使用英语。(本地化消息使用 details 域中的 LocalizedMessage 表达。关于更多详细信息,请参考本地化消息)。消息的任何动态部分 必须 作为元数据,包含在 details 中的 ErrorInfo 域。

这个消息用作问题描述。目的是让开发者理解问题。它比后面讨论得 ErrorInfo.reason 更详细。

消息 应当 使用简单易懂(没有技术惯用语)的描述性语言,清楚说明导致错误的问题,并提供可行的解决方案。

对于现存(遗留)API,如果以前返回得错误不包含机器可读得标识符, message必须 保持不变。关于更多信息,请参考更改错误消息

Status.code

code 域是状态代码, 必须google.rpc.Code 中枚举元素的数值。

例如数值 5NOT_FOUND 枚举元素的值。

Status.details

details 域允许在错误应答中包含额外错误信息消息,消息封装在 google.protobuf.Any 中。

Google为额外错误信息定义了一组标准错误细节,涵盖了API错误的大多数常见需求。如果可行,服务 应当 使用标准错误细节。

每种类型的错误细节 必须 最多包含一条。例如 details不得 存在多个 BadRequest 消息,但 可以 包含一个 BadRequest 和一个 PreconditionFailure

所有错误应答 必须details 中包含 ErrorInfo 。它提供了机器可读标识符,方便用户编写代码来处理错误。

以下部分描述了最常见的标准错误细节。

ErrorInfo

ErrorInfo消息是返回机器可读标识符的主要方式。 应当ErrorInfometadata 中包含上下文信息, 必须 包含出现在错误消息中的上下文信息。

reason 域是错误原因的简短描述,使用 snake_case 风格。错误原因在特定错误域中是唯一的。原因 必须 最多包含63个字符,符合正则表达式 [A-Z][A-Z0-9_]+[A-Z0-9] 。(这是UPPER_SNAKE_CASE字符串,没有前导或尾随下划线,也没有前导数字。)

原因 应当 简洁而充分,足够开发者理解具体内容。

正例:

  • CPU_AVAILABILITY
  • NO_STOCK
  • CHECKED_OUT
  • AVAILABILITY_ERROR

反例:

  • THE_BOOK_YOU_WANT_IS_NOT_AVAILABLE (过长)
  • ERROR (太笼统)

domain 域是 reason 所属的逻辑分组, 必须 是全局唯一的,通常使用生成错误的服务的名字,如 pubsub.googleapis.com

二元对(原因,域)构成一种机器可读的识别特定错误的方式。服务 必须 对相同错误使用同一个(原因,域)对, 不得 对逻辑上不同的错误使用同一个二元对。关于两个错误是否“相同”的判断并非总是明确的,不过通常 应当 根据客户端可能采取的操作进行区分。

metadata 域是提供额外动态信息作为上下文的键值对映射。 metadata 中的每个键 必须 最多64个字符,符合正则表达式 [a-z][a-zA-Z0-9-_]+

任何对 Status.messageLocalizedMessage.message 有帮助的请求专属信息 必须 包含在 metadata 中。这种做法非常重要,可以让机器读者不用解析错误消息,就能提取信息。

例如考虑以下消息:

当前在<us-east1-a>区域中,挂载<local-ssd=3,nvidia-t4=2>的<e2-medium> VM实例不可用。请考虑在<us-central1-f,us-central1-c>区域中尝试请求,这些区域目前容量充足。或者您可以尝试使用不同的VM硬件配置,或在稍后再次尝试请求。更多信息请参考故障排除文档。

这个错误的 ErrorInfo.metadata 映射可以是:

  • "zone": "us-east1-a"
  • "vmType": "e2-medium"
  • "attachment": "local-ssd=3,nvidia-t4=2"
  • "zonesWithCapacity": "us-central1-f,us-central1-c"

没有出现在错误消息中的上下文信息也 可以 包含在 metadata 中,以便客户端编程使用。

元数据包含的任何(原因,域)二元对可以随时改变:

  • 可以包含新键
  • 必须 继续包含全部现有键 (但可以具有空值)

换句话说,一旦用户发现一个特定的(原因,域)对,服务 必须 允许客户端在未来继续依赖这个二元对。

(原因,域)对中键的值是相互独立的,但服务 应当 争取统一的键名字。例如同一域中的两个错误原因不应分别使用 vmTypevirtualMachineType 元数据键。

本地化消息

google.rpc.LocalizedMessage 应当 尽量提供用户指定地区的本地化错误消息。

如果 Status.message 域的值并非最优,但由于更改错误消息部分的限制无法改变,那么 LocalizedMessage 可以 用来提供更友好的错误消息,即使用户没有指定地区设置。

无论消息的地区设置是如何判定的,*必须* 返回 localemessage 域。

locale 域设定了消息的地区设置,遵守IETF bcp47(语言标识标签)。示例: "en-US""fr-CH""es-MX"

message 域包含本地化文本。 应当 包括错误简介和解决问题的行动方案。消息 应当 包括上下文信息,让内容尽量更具体。消息中的上下文信息 必须 包含在 ErrorInfo.metadata 中。关于如何在消息中包含上下文信息,以及相应的元数据的更多细节,请参考 ErrorInfo

LocalizedMessage 内容 应当 包含完整的错误解决方案。如果需要的信息大小超过了本地化消息内容的适当尺寸, 必须Help 内容中提供详细解决方案信息,请参考帮助部分。

帮助

如果文本错误消息( Status.messageLocalizedMessage.message )无法为用户提供充分的上下文信息或操作引导;或者在排查故障时,需要检查多个故障点, 必须Help 内容中提供指向故障排查文档的链接。

提供帮助信息作为明确问题定义和可行解决方案的补充,而非备选。链接目标文档 必须 与错误有明确的关系。如果在单一页面中包含了多个错误的信息,*必须* 使用 ErrorInfo.reason 值缩小信息范围。

description 域是链接信息的文字说明, 必须 适合作为超链接文本展示给用户, 必须 使用纯文本(而非HTML、Markdown等)。

description 值示例: "STOCKOUT错误的故障排除文档"

url 域是链接的URL, 必须 是绝对URL,包括URI方案(scheme)。

url 值示例: "https://cloud.google.com/compute/docs/resource-error"

提供公开文档的服务,即使接口存在访问控制,链接的内容 必须 允许匿名访问。

提供内部文档的服务,链接的内容 可以 要求身份验证。

错误消息

文字错误消息可以记录在 Status.messageLocalizedMessage.message 域中。消息 应当 简洁可行。适当时,通过请求专属信息(如资源名字或地区)提供精确细节。请求专属细节 必须 记录在 ErrorInfo.metadata 中。

更改错误消息

更改 Status.message 内容时必须小心,以免破坏那些依赖消息获得所有信息的旧客户端。关于更多细节请参考理由部分

对于给定的远程过程调用:

  • 如果远程过程调用 始终 返回 ErrorInfo ,带有机器可读信息,那么 可以 随时修改 Status.message 内容。(例如,API生产者可以提供更清晰的说明,或更多请求专属信息。)
  • 其他情况下 Status.message 内容 必须 保持稳定,提供同样得文本和请求专属信息。API 应当Status.details 中包含 LocalizedMessage ,而不是更改 Status.message

即使远程过程调用始终返回 ErrorInfo ,API也 可以 保持现有 Status.message 稳定,并在 Status.details 中添加 LocalizedMessage

LocalizedMessage.details 内容 可以 随时更改。

部分错误

API 不得 支持部分错误。部分错误显著增加了用户所面临的复杂性,因为它们会绕过错误代码,或者将错误代码移动到应答消息中,用户 必须 编写专门的错误处理逻辑解决问题。

然而,有时的确需要部分错误,特别在批量操作中。因为单个条目存在问题,让大批量操作全部失败,对用户很不友好。

需要部分错误的方法 应当 使用耗时操作应当 将部分失败信息保存在元数据消息中。错误本身 必须 仍然用 google.rpc.Status 对象表示。

权限被拒绝

如果用户没有权限访问资源或上级资源,无论资源是否存在,服务 必须 返回 PERMISSION_DENIED (HTTP 403)错误。权限检查 必须 优先于资源或上级资源存在性检查。

如果用户拥有权限,但请求的资源或上级资源不存在,服务 必须 返回 NOT_FOUND (HTTP 404)错误。

HTTP/1.1和JSON表示

如果客户端使用HTTP/1.1协议(如AIP-127),错误信息在应答正文(body)中,以JSON对象返回。出于向后兼容的考虑,JSON没有精确映射 google.rpc.Status 模式,但包含的核心信息是相同的。模式使用以下的proto定义:

message Error {
  message Status {
    // The HTTP status code that corresponds to `google.rpc.Status.code`.
    int32 code = 1;
    // This corresponds to `google.rpc.Status.message`.
    string message = 2;
    // This is the enum version for `google.rpc.Status.code`.
    google.rpc.Code status = 4;
    // This corresponds to `google.rpc.Status.details`.
    repeated google.protobuf.Any details = 5;
  }

  Status error = 1;
}

最重要的区别是JSON中的 code 域是HTTP状态码, 不是 google.rpc.Status.code 数值。例如 code 数值是5的 google.rpc.Status 消息,将会转换成下面的JSON对象(只展示和code相关的域):

{
  "error": {
    "code": 404,          // The HTTP status code for "not found"
    "status": "NOT_FOUND" // The name in google.rpc.Code for value 5
  }
}

以下JSON显示了使用HTTP/1.1和JSON表示的完整的错误应答。

{
  "error": {
    "code": 429,
    "message": "The zone 'us-east1-a' does not have enough resources available to fulfill the request. Try a different zone, or try again later.",
    "status": "RESOURCE_EXHAUSTED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "RESOURCE_AVAILABILITY",
        "domain": "compute.googleapis.com",
        "metadata": {
          "zone": "us-east1-a",
          "vmType": "e2-medium",
          "attachment": "local-ssd=3,nvidia-t4=2",
          "zonesWithCapacity": "us-central1-f,us-central1-c"
        }
      },
      {
        "@type": "type.googleapis.com/google.rpc.LocalizedMessage",
        "locale": "en-US",
        "message": "An <e2-medium> VM instance with <local-ssd=3,nvidia-t4=2> is currently unavailable in the <us-east1-a> zone. Consider trying your request in the <us-central1-f,us-central1-c> zone(s), which currently has/have capacity to accommodate your request. Alternatively, you can try your request again with a different VM hardware configuration or at a later time. For more information, see the troubleshooting documentation."
      },
      {
        "@type": "type.googleapis.com/google.rpc.Help",
        "links": [
          {
            "description": "Additional information on this error",
            "url": "https://cloud.google.com/compute/docs/resource-error"
          }
        ]
      }
    ]
  }
}

理由

要求ErrorInfo

要求 ErrorInfo 的原因在于可以具体识别错误。由于 Status.status 只有大约二十个可用值,很难在整个API服务中区分不同的错误。

此外,错误消息通常包含动态部分,表达可变信息。因此需要错误应答包含机器可读组件,让客户端能够以编程方式使用这些信息。

包含LocalizedMessage

LocalizedMessage 是展示备选错误消息的地方。虽然 LocalizedMessage 可以 使用请求中设定的地区设置,但服务也 可以 在用户未设定时提供 LocalizedMessage ,这通常是为了在无法修改 Status.message 的情况下,提供更好的错误消息。如果用户没有设定地区设置, 应当 使用 en-US (美国英语)。

如果服务不支持用户设定的地区设置,也 可以 包含 LocalizedMessage ,即使已经在 Status.message 中提供了相同的消息。 原因包括:

  • 表示将在不久之后支持用户设定的地区设置,允许客户端统一使用 LocalizedMessage ,并且可以在服务支持这个地区时,不用修改错误报告代码。
  • 保持服务内所有远程过程调用的一致性:如果一些远程过程调用包含 LocalizedMessage ,而另一些只有 Status.message 错误消息,客户端必须知道哪些远程过程调用如果返回错误消息,或实现回退机制。对所有远程过程调用提供 LocalizedMessage ,让客户端代码简单统一。

更新Status.message

如果客户端发现某个错误包含 Status.message ,但缺少 ErrorInfo ,客户端开发者可能不得不解析 Status.message ,找出 Status.code 之外的信息。可能通过匹配特定文本(例如“连接因未知原因关闭”)或解析消息寻找元数据(例如资源不足的地区)得到额外信息。此时 Status.message 隐含成为API约定的一部分,因此 不得 更改。这是一个破坏性更改。这也是将 LocalizedMessage 引入 Status.details 的原因之一。

始终 包含 ErrorInfo 的远程过程调用则处于更好的位置:约定更多的是关于错误中 ErrorInfo 的稳定性。原因和域需要持续保持一致,并且只能扩展为特定(原因,域)提供的元数据。客户端仍然可能解析 Status.message ,而非使用 ErrorInfo 。但它们始终拥有更稳健的选项。

进一步阅读

  • 关于哪些错误码可以重发,请参考AIP-194
  • 关于如何在客户端库中重发请求,请参考AIP-4221

修订记录

  • 2024-10-18 重写/重组,提高清晰度。
  • 2024-01-10 包含编写有效消息的指南。
  • 2023-05-17Status.message 的推荐语言从英语改为服务的母语。
  • 2023-05-17 指定更改错误消息的要求。
  • 2023-05-10 要求所有错误应答包含 ErrorInfo
  • 2023-05-04 要求错误细节信息的消息类型是唯一的。
  • 2022-11-04 添加关于PERMISSION_DENIED错误的指南,以前包含再其他AIP中。
  • 2022-08-12 重新措辞/简化介绍,增加意图的清晰度。
  • 2020-01-22 添加对 ErrorInfo 消息的引用。
  • 2019-10-14 添加存在机器可读标识符时,允许错误消息可变的指南。
  • 2019-09-23 添加关于错误消息字符串可以更改的指南。