AIP-193
错误
有效的错误沟通是设计简单直观API的重要组成部分。服务返回标准化的错误响应使API客户端能够构建集中化的通用错误处理逻辑。这种通用逻辑简化了API客户端应用程序,并消除了繁琐的自定义错误处理代码的需求。
指导原则
服务在发生API错误时**必须**返回一个[`google.rpc.Status`][Status]消息,并且**必须**使用[`google.rpc.Code`][Code]中定义的规范错误代码。有关特定代码的更多信息,请参阅[gRPC状态代码文档][]。
错误消息**应该**帮助技术用户*理解*并*解决*问题,并且**不应该**假设用户是特定API的专家。此外,错误消息**不得**假设用户了解其底层实现。
错误消息**应该**简洁但可操作。任何额外信息**应该**在`details`字段中提供。如果需要更多信息,**应该**提供一个链接,读者可以在该链接上获取更多信息或提出问题以帮助解决问题。在编写消息时,[设置正确的语气][writing-tone]也很重要。
以下部分描述了`google.rpc.Status`的字段。
Status.message
`message`字段是面向开发者的、人类可读的“调试消息”,**应该**使用英语。(本地化消息使用`details`字段中的`LocalizedMessage`表示。有关更多详细信息,请参阅`LocalizedMessage`。)消息的任何动态方面**必须**作为元数据包含在`details`中出现的`ErrorInfo`中。
消息被视为问题描述。它旨在让开发者理解问题,并且比[`ErrorInfo.reason`][ErrorInfo-reason]更详细,稍后讨论`ErrorInfo`。
消息**应该**使用简单易懂的描述性语言(没有技术术语),清楚地说明导致错误的问题,并提供可操作的解决方案。
对于以前返回错误但没有机器可读标识符的现有(brownfield)API,`message`的值**必须**保持不变。有关更多信息,请参阅更改错误消息。
Status.code
`code`字段是状态代码,**必须**是[`google.rpc.Code`][Code]枚举中元素的数值。
例如,值`5`是`NOT_FOUND`枚举元素的数值。
Status.details
`details`字段允许在错误响应中包含带有额外错误信息的消息,每条消息都打包在`google.protobuf.Any`消息中。
Google定义了一组[标准详细负载][details]用于错误详细信息,涵盖了API错误的大多数常见需求。服务**应该**在可行时使用这些标准详细负载。
每种类型的详细负载**必须**最多包含一次。例如,`details`中**不得**有多个[`BadRequest`][BadRequest]消息,但**可以**有一个`BadRequest`和一个[`PreconditionFailure`][PreconditionFailure]。
所有错误响应**必须**在`details`中包含一个`ErrorInfo`。这提供了机器可读的标识符,以便用户可以编写代码来处理错误的特定方面。
以下部分描述了最常见的标准详细负载。
-
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`的元数据键。
-
LocalizedMessage
[`google.rpc.LocalizedMessage`][LocalizedMessage]用于提供错误消息,**应该**尽可能本地化为用户指定的区域设置。
如果`Status.message`字段具有由于更改错误消息部分中的约束而无法更改的次优值,则`LocalizedMessage`**可以**用于提供更好的错误消息,即使没有用户指定的区域设置可用。
无论消息的区域设置是如何确定的,`locale`和`message`字段**必须**都填充。
`locale`字段指定消息的区域设置,遵循IETF bcp47(语言标识标签)。示例值:`“en-US”`,`“fr-CH”`,`“es-MX”`。
`message`字段包含本地化文本本身。这**应该**包括错误的简要描述和解决错误的行动呼吁。消息**应该**包括上下文信息,以使消息尽可能具体。消息中的任何上下文信息**必须**包含在`ErrorInfo.metadata`中。有关如何在消息中包含上下文信息以及相应的元数据的更多详细信息,请参阅`ErrorInfo`。
`LocalizedMessage`负载**应该**包含错误的完整解决方案。如果需要的信息比合理适合此负载的更多,则必须在`Help`负载中提供额外的解决方案信息。有关指导,请参阅Help部分。
-
Help
当其他文本错误消息(在`Status.message`或`LocalizedMessage.message`中)没有为用户提供足够的上下文或可操作的下一步,或者如果有多个故障点需要在故障排除中考虑时,**必须**在`Help`负载中提供指向补充故障排除文档的链接。
提供此信息作为清晰问题定义和可操作解决方案的补充,而不是替代。链接的文档**必须**与错误明确相关。如果单个页面包含有关多个错误的信息,则必须使用`ErrorInfo.reason`值来缩小相关信息范围。
`description`字段是链接信息的文本描述。这**必须**适合作为超链接的文本显示给用户。这**必须**是纯文本(不是HTML,Markdown等)。
示例`description`值:`“STOCKOUT错误的故障排除文档”`
`url`字段是链接的URL。这**必须**是绝对URL,包括方案。
示例`url`值: `"https://cloud.google.com/compute/docs/resource-error"`
对于公开记录的服务,即使实际使用有访问控制,链接的内容**必须**无需身份验证即可访问。
对于私有记录的服务,链接的内容**可以**需要身份验证。
错误消息
文本错误消息可以出现在`Status.message`和`LocalizedMessage.message`字段中。消息**应该**简洁但可操作,请求特定信息(如资源名称或区域)在适当情况下提供精确细节。任何请求特定细节**必须**出现在`ErrorInfo.metadata`中。
更改错误消息
随着时间的推移更改`Status.message`的内容必须小心进行,以避免破坏以前不得不依赖消息获取所有信息的客户端。有关更多详细信息,请参阅理由部分。
对于给定的RPC:
- 如果RPC*始终*返回带有机器可读信息的`ErrorInfo`,则`Status.message`的内容**可以**随时间更改。(例如,API生产者可能提供更清晰的解释,或更多请求特定信息。)
- 否则,`Status.message`的内容**必须**稳定,提供相同的文本和相同的请求特定信息。API**应该**在`Status.details`中包含`LocalizedMessage`,而不是更改`Status.message`。
即使RPC始终返回`ErrorInfo`,API**可以**保持现有的`Status.message`稳定,并在`Status.details`中添加`LocalizedMessage`。
`LocalizedMessage.details`的内容**可以**随时间更改。
部分错误
API**不应该**支持部分错误。部分错误为用户增加了显著的复杂性,因为它们通常绕过了错误代码的使用,或将那些错误代码移动到响应消息中,用户**必须**编写专门的错误处理逻辑来解决问题。
然而,有时部分错误是必要的,特别是在批量操作中,因为单个条目的问题而失败整个大请求对用户来说是不友好的。
需要部分错误的方法**应该**使用[长时间运行的操作][],并且该方法**应该**将部分失败信息放在元数据消息中。错误本身**必须**仍然用[`google.rpc.Status`][Status]对象表示。
权限被拒绝
如果用户没有访问资源或父资源的权限,无论它是否存在,服务**必须**错误返回`PERMISSION_DENIED`(HTTP 403)。权限**必须**在检查资源或父资源是否存在之前进行检查。
如果用户有适当的权限,但请求的资源或父资源不存在,服务**必须**错误返回`NOT_FOUND`(HTTP 404)。
HTTP/1.1+JSON表示
当客户端使用HTTP/1.1时,如AIP-127所述,错误信息在响应体中作为JSON对象返回。出于向后兼容的原因,这并不精确映射到`google.rpc.Status`,但包含相同的核心信息。模式在以下proto中定义:
```proto message Error { message Status { / 对应于`google.rpc.Status.code`的HTTP状态代码。 int32 code = 1; / 这对应于`google.rpc.Status.message`。 string message = 2; / 这是`google.rpc.Status.code`的枚举版本。 google.rpc.Code status = 4; / 这对应于`google.rpc.Status.details`。 repeated google.protobuf.Any details = 5; }
Status error = 1; } ```
最重要的区别是JSON中的`code`字段是HTTP状态代码,*不是*`google.rpc.Status.code`的直接值。例如,`google.rpc.Status`消息的`code`值为5将映射到包含以下代码相关字段的对象(以及消息,详细信息等):
```json { “error”: { “code”: 404, / HTTP状态代码为“未找到” “status”: “NOT_FOUND” / `google.rpc.Code`中值为5的名称 } } ```
以下JSON显示了错误响应的完整HTTP/1.1+JSON表示。
```json { “error”: { “code”: 429, “message”: “区域’us-east1-a’没有足够的资源来满足请求。尝试不同的区域,或稍后再试。”, “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”: “在<us-east1-a>区域中,带有<local-ssd=3,nvidia-t4=2>的<e2-medium> VM实例当前不可用。考虑在<us-central1-f,us-central1-c>区域中尝试您的请求,这些区域目前有容量来满足您的请求。或者,您可以尝试使用不同的VM硬件配置或在稍后时间再次尝试您的请求。有关更多信息,请参阅故障排除文档。” }, { “@type”: “type.googleapis.com/google.rpc.Help”, “links”: [ { “description”: “有关此错误的更多信息”, “url”: “https://cloud.google.com/compute/docs/resource-error” } ] } ] } } ```
理由
要求ErrorInfo
`ErrorInfo`是必需的,因为它进一步识别错误。由于`Status.status`只有大约二十个[可用值][Code],很难在整个[API服务][API服务]中区分一个错误与另一个错误。
此外,错误消息通常包含表达可变信息的动态部分,因此需要每个错误响应都有一个机器可读的组件,使客户端能够以编程方式使用这些信息。
包含LocalizedMessage
`LocalizedMessage`被选为呈现替代错误消息的位置。虽然`LocalizedMessage`**可以**使用请求中指定的区域设置,但服务**可以**在没有用户指定区域设置的情况下提供`LocalizedMessage`,通常是为了在`Status.message`无法更改的情况下提供更好的错误消息。如果区域设置不是由用户指定的,则**应该**是`en-US`(美国英语)。
服务**可以**包含`LocalizedMessage`,即使相同的消息在`Status.message`中提供,并且不支持用户指定的本地化。原因包括:
- 意图在不久的将来支持用户指定的本地化,允许客户端一致地使用`LocalizedMessage`,并在引入功能时不更改其错误报告代码。
- 服务内所有RPC的一致性:如果某些RPC包含`LocalizedMessage`,而某些仅使用`Status.message`进行错误消息,客户端必须知道哪些RPC会做什么,或实现回退机制。在所有RPC上提供`LocalizedMessage`允许编写简单一致的客户端代码。
更新Status.message
如果客户端曾经观察到带有`Status.message`填充的错误(它总是会填充)但没有`ErrorInfo`,则该客户端的开发人员可能不得不解析`Status.message`以找出超出`Status.code`传达的信息。该信息可能通过匹配特定文本(例如“连接因未知原因关闭”)或解析消息以找出元数据值(例如资源不足的区域)找到。此时,`Status.message`隐式成为API合同的一部分,因此**不得**更新——这将是一个破坏性更改。这是将`LocalizedMessage`引入`Status.details`的原因之一。
**始终**包含`ErrorInfo`的RPC处于更好的位置:合同更多的是关于`ErrorInfo`对于任何给定错误的稳定性。原因和域需要随时间保持一致,并且为任何给定(原因,域)提供的元数据只能扩展。客户端仍然可能解析`Status.message`而不是使用`ErrorInfo`,但它们始终有一个更强大的选项可用。
进一步阅读
- 有关哪些错误代码可以重试,请参阅[AIP-194][aip-194]。
- 有关如何在客户端库中重试错误,请参阅[AIP-4221][aip-4221]。
变更日志
- **2024-10-18**:重写/重组以提高清晰度。
- **2024-01-10**:纳入编写有效消息的指导。
- **2023-05-17**:将`Status.message`的推荐语言更改为服务的母语而不是英语。
- **2023-05-17**:指定更改错误消息的要求。
- **2023-05-10**:要求所有错误响应包含[`ErrorInfo`][ErrorInfo]。
- **2023-05-04**:要求错误详细信息的消息类型唯一性。
- **2022-11-04**:添加了关于PERMISSION_DENIED错误的指导,以前在其他AIP中找到。
- **2022-08-12**:重新措辞/简化介绍以增加意图的清晰度。
- **2020-01-22**:添加了对[`ErrorInfo`][ErrorInfo]消息的引用。
- **2019-10-14**:添加了限制错误消息可变性的指导,如果有机器可读标识符存在。
- **2019-09-23**:添加了关于错误消息字符串可以更改的指导。