了解 API 错误

本指南介绍了 Data Manager API 如何处理和传达错误。 了解 API 错误的结构和含义对于构建稳健的应用至关重要,这些应用可以妥善处理从输入无效到服务暂时不可用的问题。

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

  • 数字错误代码。
  • 错误消息。
  • 可选的其他错误详情。

规范错误代码

Data Manager API 使用一组由 gRPC 和 HTTP 定义的规范错误代码。这些代码提供了错误类型的高级指示。 您应始终先检查此代码,以了解问题的基本性质。

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

快速失败模型

Data Manager API 使用快速失败模型。如果请求中的任何记录未能通过基本验证,则整个请求都会失败,并且 API 不会处理该请求中的任何数据。

快速失败模型不同于某些其他 Google API(例如 Google Ads APICampaign Manager 360 API)中的部分失败模型。 在部分失败模型中,即使某些记录存在错误,请求也会成功,并且响应包含失败记录的错误详情。

虽然部分失败可能很方便,但它存在重大风险,因为部分失败模型不会 主动 向您发出错误提醒,您必须明确检查每个响应中的错误。这可能会掩盖重要问题,因为即使 API 拒绝了请求中的许多甚至所有记录,请求也会成功。如果请求中的很大一部分记录存在错误,但您没有检查响应,您可能完全不知道数据存在广泛问题,并且只有在几天或几周后累计结果与您的预期不符时才会发现这些问题。

快速失败模型会立即提醒您数据或集成存在问题,从而避免这些陷阱,以便您采取适当的措施。

处理错误

当请求失败时,请按照以下步骤操作:

  1. 检查错误代码以查找错误类型。

    • 如果您使用 gRPC,则错误代码位于 code 字段的 Status 中。 如果您使用客户端库,它可能会抛出与错误代码对应的 特定类型的异常。例如,如果错误代码为 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,如果您需要 与支持团队联系,这将非常有用。
    • HelpLocalizedMessage 包含链接和其他详情,可帮助您解决错误。

    此外,BadRequest 载荷对于 INVALID_ARGUMENT 错误非常有用,因为它提供了有关哪些字段导致错误的信息。

标准详情载荷

Data Manager API 最常见的标准详情载荷包括:

BadRequest

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

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

field

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

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

例如,destinations[0].operating_account.account_idaccount_idoperating_account 的第一个项的 destinations 列表中。

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"
          }
        ]
      }
    ]
  }
}

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.cloud.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.cloud.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.cloud.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.cloud.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 API Console API activation",
            "url": "https://console.cloud.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.ErrorInfo)
        {
            Google.Rpc.ErrorInfo errorInfo = (Google.Rpc.ErrorInfo)detail;
            // Log the errorInfo.Reason and errorInfo.Metadata...

            // Log the details in the 'Metadata' map...
            foreach (
                KeyValuePair<String, String> metadataEntry in errorInfo.Metadata
            )
            {
                // Log the metadataEntry.Key and metadataEntry.Value...
            }
        }
        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.ApiException apiException) {
  // Fallback exception handler for other types of ApiException.

  // Gets the standard ErrorInfo payload from the exception.
  ErrorInfo errorInfo = apiException.getErrorDetails().getErrorInfo();
  // Log the 'reason' and 'domain'...

  // Log the details in the 'metadata' map...
  for (Entry<String, String> metadataEntry : errorInfo.getMetadataMap().entrySet()) {
    // Log the metadataEntry key and value...
  }

  // Gets the standard RequestInfo payload from the exception.
  RequestInfo requestInfo = invalidArgumentException.getErrorDetails().getRequestInfo();
  if (requestInfo != null) {
    String requestId = requestInfo.getRequestId();
    // Log the requestId...
  }
  ...
}

错误处理最佳实践

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

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

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

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

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

  • 仅针对暂时性服务器错误(例如 UNAVAILABLEDEADLINE_EXCEEDEDINTERNALUNKNOWNABORTED)进行重试
  • 使用指数退避算法来等待重试之间的时间间隔不断增加 。这有助于避免使已经压力过大的服务不堪重负。例如,等待 1 秒,然后等待 2 秒,再等待 4 秒,以此类推,直到达到最大重试次数或总等待时间。
  • 向退避延迟添加少量随机“抖动”,以防止许多客户端同时重试而导致“惊群”问题。
彻底记录

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

提供用户反馈

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

遵循这些准则,您可以有效地诊断和处理 Data Manager API 返回的错误,从而获得更稳定且用户友好的应用。