了解 API 错误

本指南介绍了 Data Manager API 如何处理和传达错误。了解 API 错误的结构和含义对于构建能够妥善处理各种问题的强大应用至关重要,这些问题包括输入无效和临时服务不可用等。

Data Manager API 遵循基于 gRPC 状态代码的标准 Google API 错误模型。导致错误的每个 API 响应都包含一个 Status 对象,其中包含:

  • 一个数值错误代码。
  • 错误消息。
  • 可选,其他错误详细信息。

规范错误代码

Data Manager API 使用 gRPC 和 HTTP 定义的一组规范错误代码。这些代码可大致指明错误类型。您应始终先检查此代码,以了解问题的根本性质。

如需详细了解这些代码,请参阅 API 设计指南 - 错误代码

处理错误

如果请求失败,请按以下步骤操作:

  1. 查看错误代码,找出错误类型。

    • 如果您使用 gRPC,则错误代码位于 Statuscode 字段中。 如果您使用客户端库,该库可能会抛出与相应错误代码对应的特定类型的异常。例如,如果错误代码为 INVALID_ARGUMENT,Java 版客户端库会抛出 com.google.api.gax.rpc.InvalidArgumentException
    • 如果您使用 REST,则错误代码位于 error.status 的错误响应中,相应的 HTTP 状态位于 error.code
  2. 检查错误代码的标准详情载荷。标准详情载荷是一组用于 Google API 错误的讯息。它们以结构化且一致的方式提供错误详情。Data Manager API 中的每个错误都可能包含多条标准详细信息载荷消息。Data Manager API 客户端库具有辅助方法,可用于从错误中获取标准详细信息载荷。

    无论错误代码是什么,我们都建议您检查并记录 ErrorInfoRequestInfoHelpLocalizedMessage 载荷。

    • ErrorInfo 包含可能不在其他载荷中的信息。
    • RequestInfo 包含请求 ID,如果您需要与支持团队联系,此 ID 会很有用。
    • HelpLocalizedMessage 包含链接和其他详细信息,可帮助您解决错误。

    此外,BadRequestQuotaFailureRetryInfo 载荷对于特定错误代码非常有用:

    • 如果状态代码为 INVALID_ARGUMENT,请检查 BadRequest 载荷,了解哪些字段导致了错误。
    • 如果状态代码为 RESOURCE_EXHAUSTED,请检查 QuotaFailureRetryInfo 载荷,了解配额信息和重试延迟建议。

标准详情载荷

数据管理器 API 最常见的标准详细信息载荷包括:

BadRequest

当请求失败并显示 INVALID_ARGUMENT(HTTP 状态代码 400)时,检查 BadRequest 载荷。

BadRequest 消息表示请求中的字段包含错误的值,或者缺少必需字段的值。查看 BadRequest 中的 field_violations 列表,找出哪些字段存在错误。每个 field_violations 条目都包含可帮助您修复错误的信息:

field

请求中字段的位置,使用驼峰式大小写路径语法。

如果路径指向列表(repeated 字段)中的某个项,则其索引会显示在列表名称后面的方括号 ([...]) 中。

例如,destinations[0].operating_account.account_iddestinations 列表中第一个元素的 operating_account 中的 account_id

description

说明相应值导致错误的原因。

reason

ErrorReason 枚举,例如 INVALID_HEX_ENCODINGINVALID_CURRENCY_CODE

BadRequest”的示例

以下是包含 BadRequest 消息的 INVALID_ARGUMENT 错误的示例响应。field_violations 显示的错误是 accountId 不是数字。fielddestinations[0].login_account.account_id 表示存在字段违规的 accountId 位于 destinations 列表中的第一个项的 login_account 中。

{
  "error": {
    "code": 400,
    "message": "There was a problem with the request.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "INVALID_ARGUMENT",
        "domain": "datamanager.googleapis.com",
        "metadata": {
          "requestId": "t-a8896317-069f-4198-afed-182a3872a660"
        }
      },
      {
        "@type": "type.googleapis.com/google.rpc.RequestInfo",
        "requestId": "t-a8896317-069f-4198-afed-182a3872a660"
      },
      {
        "@type": "type.googleapis.com/google.rpc.BadRequest",
        "fieldViolations": [
          {
            "field": "destinations[0].login_account.account_id",
            "description": "String is not a valid number.",
            "reason": "INVALID_NUMBER_FORMAT"
          }
        ]
      }
    ]
  }
}

以下是另一个示例响应,其中包含 BadRequest 消息的 INVALID_ARGUMENT 错误。在这种情况下,field_violations 列表会显示两个错误:

  1. 第一个 event 在事件的第二个用户标识符上具有未进行十六进制编码的值。

  2. 第二个 event 的值未在事件的第三个用户标识符上进行十六进制编码。

{
  "error": {
    "code": 400,
    "message": "There was a problem with the request.",
    "status": "INVALID_ARGUMENT",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "INVALID_ARGUMENT",
        "domain": "datamanager.googleapis.com",
        "metadata": {
          "requestId": "t-6bc8fb83-d648-4942-9c49-2604276638d8"
        }
      },
      {
        "@type": "type.googleapis.com/google.rpc.RequestInfo",
        "requestId": "t-6bc8fb83-d648-4942-9c49-2604276638d8"
      },
      {
        "@type": "type.googleapis.com/google.rpc.BadRequest",
        "fieldViolations": [
          {
            "field": "events.events[0].user_data.user_identifiers[1]",
            "description": "The HEX encoded value is malformed.",
            "reason": "INVALID_HEX_ENCODING"
          },
          {
            "field": "events.events[1].user_data.user_identifiers[2]",
            "description": "The HEX encoded value is malformed.",
            "reason": "INVALID_HEX_ENCODING"
          }
        ]
      }
    ]
  }
}

QuotaFailureRetryInfo

当请求因 RESOURCE_EXHAUSTED(HTTP 状态代码 429)而失败时,检查 QuotaFailureRetryInfo 载荷。

QuotaFailure 消息表示资源已耗尽(例如,您已超出配额),或者系统过载。检查 violations 列表,确定超出了哪些配额。

错误还可能包含 RetryInfo 消息,该消息表示建议的重试请求的 retry_delay

RequestInfo

每当请求失败时,检查是否存在 RequestInfo 载荷。RequestInfo 包含唯一标识 API 请求的 request_id

{
  "@type": "type.googleapis.com/google.rpc.RequestInfo",
  "requestId": "t-4490c640-dc5d-4c28-91c1-04a1cae0f49f"
}

在记录错误或与支持团队联系时,请务必提供请求 ID,以便我们诊断问题。

ErrorInfo

检查 ErrorInfo 消息,以检索可能未包含在其他标准详细信息载荷中的其他信息。ErrorInfo 载荷包含一个 metadata 映射,其中包含有关错误的信息。

例如,以下是因使用未启用 Data Manager API 的 Google Cloud 项目的凭据而导致的 PERMISSION_DENIED 失败的 ErrorInfoErrorInfo 提供有关错误的其他信息,例如:

  • 与请求关联的项目,位于 metadata.consumer 下。
  • metadata.serviceTitle 下的服务的名称。
  • 可在 metadata.activationUrl 下启用该服务的网址。
{
  "error": {
    "code": 403,
    "message": "Data Manager API has not been used in project PROJECT_NUMBER before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/datamanager.googleapis.com/overview?project=PROJECT_NUMBER then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "SERVICE_DISABLED",
        "domain": "googleapis.com",
        "metadata": {
          "consumer": "projects/PROJECT_NUMBER",
          "service": "datamanager.googleapis.com",
          "containerInfo": "PROJECT_NUMBER",
          "serviceTitle": "Data Manager API",
          "activationUrl": "https://console.developers.google.com/apis/api/datamanager.googleapis.com/overview?project=PROJECT_NUMBER"
        }
      },
      ...
    ]
  }
}

HelpLocalizedMessage

检查 HelpLocalizedMessage 载荷,获取指向文档和本地化错误消息的链接,这些链接可帮助您了解并修复错误。

例如,以下是因使用未启用 Data Manager API 的 Google Cloud 项目的凭据而导致的 PERMISSION_DENIED 失败的 HelpLocalizedMessageHelp 载荷显示了可启用该服务的网址,而 LocalizedMessage 则包含错误说明。

{
  "error": {
    "code": 403,
    "message": "Data Manager API has not been used in project PROJECT_NUMBER before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/datamanager.googleapis.com/overview?project=PROJECT_NUMBER then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.LocalizedMessage",
        "locale": "en-US",
        "message": "Data Manager API has not been used in project PROJECT_NUMBER before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/datamanager.googleapis.com/overview?project=PROJECT_NUMBER then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry."
      },
      {
        "@type": "type.googleapis.com/google.rpc.Help",
        "links": [
          {
            "description": "Google developers console API activation",
            "url": "https://console.developers.google.com/apis/api/datamanager.googleapis.com/overview?project=PROJECT_NUMBER"
          }
        ]
      },
      ...
    ]
  }
}

访问错误详情

如果您使用的是某个客户端库,请使用辅助方法获取标准详细信息载荷。

.NET

try {
    // Send API request
}
catch (Grpc.Core.RpcException rpcException)
{
    Console.WriteLine($"Exception encountered: {rpcException.Message}");
    var statusDetails =
        Google.Api.Gax.Grpc.RpcExceptionExtensions.GetAllStatusDetails(
            rpcException
        );
    foreach (var detail in statusDetails)
    {
        if (detail is Google.Rpc.BadRequest)
        {
            Google.Rpc.BadRequest badRequest = (Google.Rpc.BadRequest)detail;
            foreach (
                BadRequest.Types.FieldViolation? fieldViolation in badRequest.FieldViolations
            )
            {
                // Access attributes such as fieldViolation!.Reason and fieldViolation!.Field
            }
        }
        else if (detail is Google.Rpc.RequestInfo)
        {
            Google.Rpc.RequestInfo requestInfo = (Google.Rpc.RequestInfo)detail;
            string requestId = requestInfo.RequestId;
            // Log the requestId...
        }
        else if (detail is Google.Rpc.QuotaFailure)
        {
            Google.Rpc.QuotaFailure quotaFailure = (Google.Rpc.QuotaFailure)detail;
            foreach (
                Google.Rpc.QuotaFailure.Types.Violation violation in quotaFailure.Violations
            )
            {
                // Access attributes such as violation.Subject and violation.QuotaId
            }
        }
        else
        {
            // ...
        }
    }
}

Java

try {
  // Send API request
} catch (com.google.api.gax.rpc.InvalidArgumentException invalidArgumentException) {
  // Gets the standard BadRequest payload from the exception.
  BadRequest badRequest = invalidArgumentException.getErrorDetails().getBadRequest();
  for (int i = 0; i < badRequest.getFieldViolationsCount(); i++) {
    FieldViolation fieldViolation = badRequest.getFieldViolations(i);
    // Access attributes such as fieldViolation.getField() and fieldViolation.getReason()
  }

  // Gets the standard RequestInfo payload from the exception.
  RequestInfo requestInfo = invalidArgumentException.getErrorDetails().getRequestInfo();
  if (requestInfo != null) {
    String requestId = requestInfo.getRequestId();
    // Log the requestId...
  }
} catch (com.google.api.gax.rpc.QuotaFailureException quotaFailureException) {
  // Gets the standard QuotaFailure payload from the exception.
  QuotaFailure quotaFailure = quotaFailureException.getErrorDetails().getQuotaFailure();
  for (int i = 0; i < quotaFailure.getViolationsCount(); i++) {
    QuotaFailure.Violation violation = quotaFailure.getViolations(i);
    // Access attributes such as violation.getSubject() and violation.getQuotaId()
  }

  // Gets the standard RequestInfo payload from the exception.
  RequestInfo requestInfo = quotaFailureException.getErrorDetails().getRequestInfo();
  if (requestInfo != null) {
    String requestId = requestInfo.getRequestId();
    // Log the requestId...
  }
} catch (com.google.api.gax.rpc.ApiException apiException) {
  // Fallback exception handler for other types of ApiException.
  ...
}

错误处理最佳实践

如需构建弹性应用,请实施以下最佳实践。

检查错误详情
始终查找标准详情载荷之一,例如 BadRequest。每个标准详细信息载荷都包含可帮助您了解错误原因的信息。
区分客户端错误和服务器错误

确定错误是由您的实现(客户端)问题还是 API(服务器)问题引起的。

  • 客户端错误:代码如 INVALID_ARGUMENTNOT_FOUNDPERMISSION_DENIEDFAILED_PRECONDITIONUNAUTHENTICATED。这些错误需要更改请求或应用的状态/凭据。 在解决问题之前,请勿重试请求。
  • 服务器错误:例如 UNAVAILABLEINTERNALDEADLINE_EXCEEDEDUNKNOWN 等代码。这表明 API 服务存在暂时性问题。
实现重试策略

确定错误是否可以重试,并使用重试策略。

  • 仅针对 UNAVAILABLEDEADLINE_EXCEEDEDINTERNALUNKNOWNABORTED 等暂时性服务器错误进行重试。
  • 使用指数退避算法在重试之间等待越来越长的时间。这有助于避免使已承受压力的服务不堪重负。例如,等待 1 秒,然后等待 2 秒,再等待 4 秒,依此类推,直至达到重试次数上限或总等待时间上限。
  • 在退避延迟中添加少量随机“抖动”,以防止出现“惊群”问题,即许多客户端同时重试。
详细记录

记录完整的错误响应,包括所有标准详细信息载荷,尤其是请求 ID。此信息对于调试和向 Google 支持团队报告问题(如果需要)至关重要。

提供用户反馈

根据标准详情载荷中的代码和消息,向应用的用户提供清晰且有用的反馈。例如,您可以说“缺少交易 ID”或“找不到目的地的账号 ID”,而不是只说“发生错误”。

遵循这些准则,您可以有效地诊断和处理 Data Manager API 返回的错误,从而打造更稳定、更人性化的应用。