瞭解 API 錯誤

本指南說明 Data Manager API 如何處理及傳達錯誤。瞭解 API 錯誤的結構和意義,對於建構穩健的應用程式至關重要,可讓應用程式妥善處理各種問題,包括無效輸入和暫時無法使用服務。

Data Manager API 遵循標準 Google API 錯誤模型,該模型以 gRPC 狀態碼為基礎。每個導致錯誤的 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,如需聯絡支援團隊,這項資訊會很有幫助。
    • 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」的例句

以下是 INVALID_ARGUMENT 錯誤和 BadRequest 訊息的回應範例。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"
          }
        ]
      }
    ]
  }
}

以下是另一個範例回應,其中包含 INVALID_ARGUMENT 錯誤和 BadRequest 訊息。在本例中,field_violations 清單會顯示兩項錯誤:

  1. 第一個 event 的值在事件的第二個使用者 ID 上未經十六進位編碼。

  2. 第二個 event 的值在事件的第三個使用者 ID 上未經十六進位編碼。

{
  "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 對應,其中含有錯誤相關資訊。

舉例來說,如果使用的 Google Cloud 雲端專案憑證未啟用 Data Manager API,導致 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。

如果要求超過每分鐘的攝入變更要求上限,系統會傳回 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 酬載,取得說明文件連結和本地化錯誤訊息,協助您瞭解及修正錯誤。

舉例來說,如果 Google Cloud 雲端專案未啟用 Data Manager API,但您使用該專案的憑證,就會導致 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...
  }
  ...
}

處理 gRPC 的標頭大小錯誤

如果擷取或移除要求中的大量記錄未通過驗證,Data Manager API 會在回應的 BadRequest 酬載中傳回所有錯誤。

在 gRPC 中,服務會在 HTTP/2 回應標頭 (中繼資料) 中傳回這些錯誤詳細資料。根據預設,gRPC 用戶端程式庫會限制傳入標頭的大小上限 (通常為 8 到 10 KB)。如果錯誤詳細資料超出這項限制,用戶端 HTTP/2 傳輸作業會在剖析錯誤回應前失敗,並擲回例外狀況,例如:

Http2Exception: Header size exceeded max allowed size (10240)

由於用戶端無法處理回應標頭,因此會取消串流 (在伺服器端記錄中顯示為 HTTP 狀態 499 Client Closed Request),而應用程式無法存取 BadRequest 錯誤詳細資料。

如要解決這個問題並檢查完整的錯誤清單,請設定 gRPC 用戶端,提高傳入中繼資料的大小上限 (例如 16 MB):

.NET

// Creates a client for the ingestion service. Increases the maximum metadata size to
// 16MB to prevent RESOURCE_EXHAUSTED errors when responses contain large error
// details.
IngestionServiceClient ingestionServiceClient = new IngestionServiceClientBuilder
{
    GrpcChannelOptions = GrpcChannelOptions.Empty.WithCustomOption(
        "grpc.max_metadata_size",
        16 * 1024 * 1024
    ),
}.Build();

Java

// Creates a client for the ingestion service. Increases the maximum metadata size to 16MB to
// prevent RESOURCE_EXHAUSTED errors when responses contain large error details.
InstantiatingGrpcChannelProvider provider =
    IngestionServiceSettings.defaultGrpcTransportProviderBuilder()
        .setMaxInboundMetadataSize(16 * 1024 * 1024)
        .build();
IngestionServiceSettings settings =
    IngestionServiceSettings.newBuilder().setTransportChannelProvider(provider).build();
try (IngestionServiceClient ingestionServiceClient = IngestionServiceClient.create(settings)) {
  // ...
}

節點

// Creates a client for the ingestion service. Increases the maximum metadata
// size to 16MB to prevent RESOURCE_EXHAUSTED errors when responses contain
// large error details.
const client = new IngestionServiceClient({
  'grpc.max_metadata_size': 16 * 1024 * 1024,
});

PHP

// Creates a client for the ingestion service. Increases the maximum metadata size to 16MB to
// prevent RESOURCE_EXHAUSTED errors when responses contain large error details.
$client = new IngestionServiceClient([
    'transportConfig' => [
        'grpc' => [
            'stubOpts' => [
                'grpc.max_metadata_size' => 16 * 1024 * 1024,
            ],
        ],
    ],
]);

Python

# Creates a client for the ingestion service. Increases the maximum
# metadata size to 16MB to prevent RESOURCE_EXHAUSTED errors when responses
# contain large error details.
channel = datamanager_v1.services.ingestion_service.transports.IngestionServiceGrpcTransport.create_channel(
    options=[("grpc.max_metadata_size", 16 * 1024 * 1024)]
)
transport = datamanager_v1.services.ingestion_service.transports.IngestionServiceGrpcTransport(
    channel=channel
)
client: datamanager_v1.IngestionServiceClient = (
    datamanager_v1.IngestionServiceClient(transport=transport)
)

錯誤處理的最佳做法

如要建構具備韌性的應用程式,請實作下列最佳做法。

傳送前先驗證
建構或變更整合時,請傳送 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 傳回的錯誤,進而打造更穩定、更容易使用的應用程式。