瞭解 API 錯誤

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

Data Manager API 遵循標準 Google API 錯誤模型,該模型以 gRPC 狀態碼為基礎。如果 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,如需與支援團隊聯絡,這項資訊會很有幫助。
    • HelpLocalizedMessage 包含連結和其他詳細資料,可協助您解決錯誤。

    此外,BadRequestQuotaFailureRetryInfo 酬載適用於特定錯誤代碼:

    • 如果狀態碼為 INVALID_ARGUMENT,請檢查 BadRequest 酬載,瞭解導致錯誤的欄位。
    • 如果狀態碼為 RESOURCE_EXHAUSTED,請檢查 QuotaFailureRetryInfo 酬載的配額資訊,以及重試延遲建議。

標準詳細資料酬載

Data Manager API 最常見的標準詳細資料酬載如下:

BadRequest

如果要求失敗並傳回 INVALID_ARGUMENT (HTTP 狀態碼 400),請檢查 BadRequest 酬載。

BadRequest 訊息表示要求中的欄位含有錯誤值,或是缺少必填欄位的值。請查看 field_violations 清單中的錯誤訊息,找出有錯誤的欄位。BadRequest每個 field_violations 項目都包含有助於修正錯誤的資訊:

field

要求中欄位的位置,使用駝峰式大小寫路徑語法。

如果路徑指向清單中的項目 (repeated 欄位),其索引會顯示在清單名稱後的方括號 ([...]) 中。

舉例來說,destinations[0].operating_account.account_iddestinations 清單中第一個項目的 operating_account account_id

description

說明該值導致錯誤的原因。

reason

ErrorReason 列舉,例如 INVALID_HEX_ENCODINGINVALID_CURRENCY_CODE

BadRequest」的例句

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

以下是 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"
          }
        ]
      }
    ]
  }
}

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

舉例來說,如果使用 Google Cloud 專案的憑證,但該專案未啟用 Data Manager API,就會導致 ErrorInfo 失敗,相關訊息如下:PERMISSION_DENIEDErrorInfo會提供錯誤的額外資訊,例如:

  • 要求相關聯的專案,位於 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 傳回的錯誤,進而打造更穩定且容易使用的應用程式。