AIP-193 错误
编号 | 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
中枚举元素的数值。
例如数值 5
是 NOT_FOUND
枚举元素的值。
Status.details
details
域允许在错误应答中包含额外错误信息消息,消息封装在 google.protobuf.Any
中。
Google为额外错误信息定义了一组标准错误细节,涵盖了API错误的大多数常见需求。如果可行,服务 应当 使用标准错误细节。
每种类型的错误细节 必须 最多包含一条。例如 details
中 不得 存在多个 BadRequest
消息,但 可以 包含一个 BadRequest
和一个 PreconditionFailure
。
所有错误应答 必须 在 details
中包含 ErrorInfo
。它提供了机器可读标识符,方便用户编写代码来处理错误。
以下部分描述了最常见的标准错误细节。
ErrorInfo
ErrorInfo
消息是返回机器可读标识符的主要方式。 应当 在 ErrorInfo
的 metadata
中包含上下文信息, 必须 包含出现在错误消息中的上下文信息。
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.message
或 LocalizedMessage.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
中,以便客户端编程使用。
元数据包含的任何(原因,域)二元对可以随时改变:
- 可以包含新键
- 必须 继续包含全部现有键 (但可以具有空值)
换句话说,一旦用户发现一个特定的(原因,域)对,服务 必须 允许客户端在未来继续依赖这个二元对。
(原因,域)对中键的值是相互独立的,但服务 应当 争取统一的键名字。例如同一域中的两个错误原因不应分别使用 vmType
和 virtualMachineType
元数据键。
本地化消息
google.rpc.LocalizedMessage
应当 尽量提供用户指定地区的本地化错误消息。
如果 Status.message
域的值并非最优,但由于更改错误消息部分的限制无法改变,那么 LocalizedMessage
可以 用来提供更友好的错误消息,即使用户没有指定地区设置。
无论消息的地区设置是如何判定的,*必须* 返回 locale
和 message
域。
locale
域设定了消息的地区设置,遵守IETF bcp47(语言标识标签)。示例: "en-US"
、 "fr-CH"
、 "es-MX"
。
message
域包含本地化文本。 应当 包括错误简介和解决问题的行动方案。消息 应当 包括上下文信息,让内容尽量更具体。消息中的上下文信息 必须 包含在 ErrorInfo.metadata
中。关于如何在消息中包含上下文信息,以及相应的元数据的更多细节,请参考 ErrorInfo
。
LocalizedMessage
内容 应当 包含完整的错误解决方案。如果需要的信息大小超过了本地化消息内容的适当尺寸, 必须 在 Help
内容中提供详细解决方案信息,请参考帮助部分。
帮助
如果文本错误消息( Status.message
或 LocalizedMessage.message
)无法为用户提供充分的上下文信息或操作引导;或者在排查故障时,需要检查多个故障点, 必须 在 Help
内容中提供指向故障排查文档的链接。
提供帮助信息作为明确问题定义和可行解决方案的补充,而非备选。链接目标文档 必须 与错误有明确的关系。如果在单一页面中包含了多个错误的信息,*必须* 使用 ErrorInfo.reason
值缩小信息范围。
description
域是链接信息的文字说明, 必须 适合作为超链接文本展示给用户, 必须 使用纯文本(而非HTML、Markdown等)。
description
值示例: "STOCKOUT错误的故障排除文档"
url
域是链接的URL, 必须 是绝对URL,包括URI方案(scheme)。
url
值示例: "https://cloud.google.com/compute/docs/resource-error"
提供公开文档的服务,即使接口存在访问控制,链接的内容 必须 允许匿名访问。
提供内部文档的服务,链接的内容 可以 要求身份验证。
错误消息
文字错误消息可以记录在 Status.message
和 LocalizedMessage.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
。但它们始终拥有更稳健的选项。
进一步阅读
修订记录
- 2024-10-18 重写/重组,提高清晰度。
- 2024-01-10 包含编写有效消息的指南。
- 2023-05-17 将
Status.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 添加关于错误消息字符串可以更改的指南。