了解 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 API 和 Campaign Manager 360 API)中的部分失败模型不同。在部分失败模型中,即使某些记录存在错误,请求也会成功,并且响应会包含失败记录的错误详细信息。

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

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

使用 validateOnly 检查快速失败错误

大多数提取和内容移除要求都支持 validateOnly 字段。当您将 validateOnly 设置为 true 时,Data Manager API 会像处理常规请求一样运行相同的基本验证检查,但不会注入或移除任何数据。

  • 如果请求有错误,则会失败,并返回与常规请求相同的错误响应。
  • 如果请求通过验证,则会成功。响应会包含选填字段的任何 fieldWarnings,就像常规请求一样。

使用 validateOnly 来执行以下操作:

  • 测试新的或更新的集成,而不会影响实时数据。
  • 在重新发送请求之前,请确认相应修复解决了错误。

处理错误

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

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

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

    无论错误代码是什么,我们都建议您检查并记录 ErrorInfo、RequestInfo、Help 和 LocalizedMessage 载荷。

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

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

注入警告

Data Manager API 会尽可能接受数据提取请求。 如果您包含非必需数据,则这些字段的验证失败不会导致请求失败。例如,如果某个购物车商品缺少商家商品 ID,API 会处理其余请求并返回警告。

成功的提取响应(HTTP 状态代码 200)会在 fieldWarnings 列表中包含这些警告。每个条目都是一个 FieldWarning 对象,包含以下字段:

field

请求中相应字段的位置,采用蛇形命名法路径语法。

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

例如,events.events[0].cart_data.items[0].merchant_product_id 会识别与请求中第一个事件的购物车数据中的第一个商品相关的警告。

description

说明所提供的值导致警告的原因。

reason

用于标识警告类型的 WarningReason 枚举值。

使用 FieldWarning 的示例

下面是一个成功处理的提取请求的示例响应,其中包含一条警告,因为某个购物车商品的商家商品 ID 缺失。

{
  "requestId": "126365e1-16d0-4c81-9de9-f362711e250a",
  "fieldWarnings": [
    {
      "field": "events.events[0].cart_data.items[0].merchant_product_id",
      "description": "The merchant product ID is missing in the cart item.",
      "reason": "WARNING_REASON_CART_DATA_ITEM_MERCHANT_PRODUCT_ID_MISSING"
    }
  ]
}

标准详情载荷

Data Manager API 最常见的标准详细信息载荷包括:

BadRequest

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

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

field

请求中相应字段的位置,采用蛇形命名法路径语法。

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

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

description

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

reason

ErrorReason 枚举,例如 INVALID_HEX_ENCODING 或 INVALID_CURRENCY_CODE。

BadRequest的示例

以下是包含 BadRequest 消息的 INVALID_ARGUMENT 错误的示例响应。field_violations 显示的错误是 accountId 不是数字。field 值 destinations[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 失败的 ErrorInfo。ErrorInfo 提供有关错误的其他信息,例如:

  • 与请求关联的项目,位于 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"
        }
      },
      ...
    ]
  }
}

配额和速率限制错误

当请求超出项目限制时,API 会返回 RESOURCE_EXHAUSTED 错误(HTTP 状态代码 429)。ErrorInfo 载荷的 metadata 映射会详细说明超出了哪项限制:

consumer
与请求关联的 Google Cloud 项目,格式为 projects/PROJECT_NUMBER。
quota_limit
超出配额限制的名称,例如 IngestionMutateRequestsPerMinutePerProject 或 IngestionMutateRequestsPerDayPerProject。您可以使用此值来确定应用是否超出了每分钟限制或每日限额。如需查看限制名称的完整列表,请参阅项目限制。
quota_location
强制执行配额的位置。对于 Data Manager API,此值始终为 global。
quota_metric
与相应限制关联的指标,例如 datamanager.googleapis.com/ingestion_mutate_requests。
service
服务的名称,datamanager.googleapis.com。

以下示例展示了当请求超出每分钟的提取 mutate 请求限额时,系统返回的 RESOURCE_EXHAUSTED 错误响应:

{
  "error": {
    "code": 429,
    "message": "Quota exceeded for quota metric 'Ingestion mutate requests' and limit 'Ingestion mutate requests per minute' of service 'datamanager.googleapis.com' for consumer 'project_number:PROJECT_NUMBER'.",
    "status": "RESOURCE_EXHAUSTED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "RATE_LIMIT_EXCEEDED",
        "domain": "googleapis.com",
        "metadata": {
          "consumer": "projects/PROJECT_NUMBER",
          "quota_limit": "IngestionMutateRequestsPerMinutePerProject",
          "quota_location": "global",
          "quota_metric": "datamanager.googleapis.com/ingestion_mutate_requests",
          "service": "datamanager.googleapis.com"
        }
      }
    ]
  }
}

Help和LocalizedMessage

检查 Help 和 LocalizedMessage 载荷,以获取指向文档和已本地化出错提示的链接,帮助您了解并修复错误。

例如,以下是因使用未启用 Data Manager API 的 Google Cloud 云项目 的凭据而导致的 PERMISSION_DENIED 失败的 Help 和 LocalizedMessage。Help 载荷显示了可启用服务的网址,而 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...

            // If handling a rate limit error, check the exceeded quota limit:
            if (errorInfo.Reason == "RATE_LIMIT_EXCEEDED" &&
                errorInfo.Metadata.TryGetValue("quota_limit", out string quotaLimit))
            {
                // Inspect quotaLimit to determine whether it is a per-minute
                // or daily limit (for example,
                // IngestionMutateRequestsPerMinutePerProject).
            }

            // 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'...

  // If handling a rate limit error, check the exceeded quota limit:
  if (errorInfo != null && "RATE_LIMIT_EXCEEDED".equals(errorInfo.getReason())) {
    String quotaLimit = errorInfo.getMetadataMap().get("quota_limit");
    // Inspect quotaLimit to determine whether it is a per-minute
    // or daily limit (for example,
    // IngestionMutateRequestsPerMinutePerProject).
  }

  // 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 = apiException.getErrorDetails().getRequestInfo();
  if (requestInfo != null) {
    String requestId = requestInfo.getRequestId();
    // Log the requestId...
  }
  ...
}

错误处理最佳实践

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

发送前请先验证
在构建或更改集成时,请发送将 validateOnly 设置为 true 的请求,以便在提取数据之前捕获快速失败错误。
检查错误详情
请务必查找标准详情载荷(例如 BadRequest)。每个标准详细信息载荷都包含有助于您了解错误原因的信息。
区分客户端错误和服务器错误

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

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

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

详细记录

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

提供用户反馈

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

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