Как анализировать ошибки API

В этом руководстве рассказывается, как API Менеджера данных обрабатывает ошибки и сообщает о них. Понимание структуры и значения ошибок API необходимо для создания надежных приложений, которые могут корректно обрабатывать проблемы, от недопустимого ввода до временной недоступности сервиса.

Data Manager API соответствует стандартной модели ошибок API Google, основанной на кодах статуса gRPC. Каждый ответ API, который приводит к ошибке, включает объект Status со следующими полями:

  • Числовой код ошибки.
  • Сообщение об ошибке.
  • Дополнительные сведения об ошибке (необязательно).

Канонические коды ошибок

В Data Manager API используется набор канонических кодов ошибок, определенных gRPC и HTTP. Эти коды позволяют получить общее представление о типе ошибки. Сначала всегда проверяйте этот код, чтобы понять суть проблемы.

Подробнее о кодах ошибок…

Модель быстрого отказа

В Data Manager API используется модель быстрого отказа. Если в запросе есть структурные ошибки или хотя бы одна запись не проходит проверку на наличие обязательного поля, запрос отклоняется целиком и API не обрабатывает никакие данные из него.

Сравнение с моделью частичного отказа

Модель быстрого отказа отличается от модели частичного отказа, используемой в некоторых других API Google, например Google Рекламы и Менеджера кампаний 360. При частичном сбое запрос выполняется, даже если в некоторых записях есть ошибки, а в ответе содержатся сведения об ошибках в этих записях.

Хотя частичный отказ может быть удобен, он несет значительные риски, поскольку модель частичного отказа не предупреждает вас об ошибках – вы должны явно проверять наличие ошибок в каждом ответе. Это может скрыть важные проблемы, поскольку запрос выполняется, даже если API отклоняет многие или все записи в нем. Если значительная часть записей в запросе содержит ошибки, но вы не проверяете ответ, то можете не знать о распространенных проблемах с данными и обнаружить их только через несколько дней или недель, когда суммарные результаты не будут соответствовать вашим ожиданиям.

Модель быстрого отказа позволяет избежать этих проблем, поскольку вы сразу же получаете уведомления о неполадках с данными или интеграцией и можете принять необходимые меры.

Как проверить наличие ошибок с помощью validateOnly

Большинство запросов на загрузку и удаление поддерживают поле validateOnly. Если задать для параметра validateOnly значение true, Data Manager API выполнит те же основные проверки, что и при обычном запросе, но не будет принимать или удалять данные.

  • Если в запросе есть ошибки, он завершится неудачно с тем же ответом об ошибке, который вы получили бы при обычном запросе.
  • Если запрос пройдет проверку, он будет выполнен. Ответ включает любые fieldWarnings для необязательных полей, как и обычный запрос.

Используя validateOnly, вы можете:

  • Проверять новые или обновленные интеграции, не затрагивая данные.
  • Убедитесь, что исправление устраняет ошибку, прежде чем отправлять запрос повторно.

Обработка ошибок

Если запрос не выполнен, сделайте следующее:

  1. Чтобы узнать тип ошибки, проверьте код ошибки.

    • Если вы используете gRPC, код ошибки находится в поле code объекта Status. Если вы используете клиентскую библиотеку, она может выдать исключение определенного типа, соответствующее коду ошибки. Например, клиентская библиотека для Java выдает исключение com.google.api.gax.rpc.InvalidArgumentException, если код ошибки – INVALID_ARGUMENT.
    • Если вы используете REST, код ошибки будет указан в ответе об ошибке в поле error.status, а соответствующий статус HTTP – в поле error.code.
  2. Проверьте стандартную полезную нагрузку сведений для кода ошибки. Стандартные полезные нагрузки с подробной информацией представляют собой набор сообщений об ошибках, возникающих в API Google. Они содержат подробную информацию об ошибках в структурированном и единообразном формате. Для каждой ошибки, полученной от Data Manager API, может быть несколько стандартных сообщений с подробной информацией. В клиентских библиотеках Data Manager API есть вспомогательные методы, которые позволяют получать стандартные полезные нагрузки с подробной информацией об ошибке.

    Независимо от кода ошибки мы рекомендуем проверить и зарегистрировать полезные нагрузки ErrorInfo, RequestInfo, Help и LocalizedMessage.

    • ErrorInfo содержит информацию, которой может не быть в других полезных нагрузках.
    • RequestInfo содержит идентификатор запроса, который может понадобиться, если вам нужно обратиться в службу поддержки.
    • Help и LocalizedMessage содержат ссылки и другую информацию, которая поможет вам устранить ошибку.

    Кроме того, полезная нагрузка BadRequest помогает устранять ошибки INVALID_ARGUMENT, поскольку содержит информацию о том, какие поля вызвали ошибку.

Предупреждения при приеме контента

Data Manager API принимает столько запросов на добавление данных, сколько возможно. Если вы добавите необязательные данные, ошибки проверки в этих полях не приведут к сбою запроса. Например, если в товаре, добавленном в корзину, отсутствует идентификатор товара продавца, API обработает остальную часть запроса и вернет предупреждение.

При успешном выполнении запроса на загрузку (код статуса HTTP 200) эти предупреждения включаются в список fieldWarnings. Каждая запись представляет собой объект FieldWarning со следующими полями:

field

Расположение поля в запросе, указанное в синтаксисе пути snake case.

Если путь указывает на элемент в списке (поле repeated), его индекс будет показан в квадратных скобках ([...]) после названия списка.

Например, events.events[0].cart_data.items[0].merchant_product_id указывает на предупреждение, связанное с первым товаром в данных корзины первого события в запросе.

description

Объяснение, почему указанное значение вызвало предупреждение.

reason

Значение перечисления WarningReason, которое определяет тип предупреждения.

Пример с карточкой "FieldWarning"

Ниже приведен пример ответа на успешный запрос на загрузку, содержащий предупреждение, поскольку для одного из товаров в корзине не был указан идентификатор товара продавца.

{
  "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

Проверьте полезную нагрузку BadRequest, если запрос не выполнен с ошибкой INVALID_ARGUMENT (код статуса HTTP 400).

Сообщение BadRequest означает, что в запросе были поля с недопустимыми значениями или отсутствовало значение для обязательного поля. Проверьте список field_violations в разделе BadRequest, чтобы узнать, в каких полях есть ошибки. Каждая запись field_violations содержит информацию, которая поможет вам устранить ошибку:

field

Расположение поля в запросе, указанное в синтаксисе пути snake case.

Если путь указывает на элемент в списке (поле repeated), его индекс будет показан в квадратных скобках ([...]) после названия списка.

Например, destinations[0].operating_account.account_id – это account_id в operating_account первого элемента списка destinations.

description

Объяснение того, почему значение вызвало ошибку.

reason

Перечисление ErrorReason, например INVALID_HEX_ENCODING или INVALID_CURRENCY_CODE.

Примеры для слова BadRequest

Ниже приведен пример ответа на ошибку INVALID_ARGUMENT с сообщением BadRequest. field_violations означает, что ошибка связана с accountId, которое не является числом. Значение field destinations[0].login_account.account_id показывает, что accountId с нарушением поля находится в login_account первого элемента в списке destinations.

{
  "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 имеет значение, которое не закодировано в шестнадцатеричном формате во втором идентификаторе пользователя события.

  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 содержит request_id, который уникальным образом идентифицирует ваш запрос к API.

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

При регистрации ошибок или обращении в службу поддержки обязательно указывайте идентификатор запроса, чтобы помочь в диагностике проблем.

ErrorInfo

Проверьте сообщение ErrorInfo, чтобы получить дополнительную информацию, которая может отсутствовать в других стандартных полезных нагрузках. Полезная нагрузка ErrorInfo содержит карту metadata с информацией об ошибке.

Например, ниже приведен ErrorInfo для ошибки PERMISSION_DENIED, вызванной использованием учетных данных для проекта Google Cloud, в котором не включен Data Manager API. В ErrorInfo содержится дополнительная информация об ошибке, например:

  • Проект, связанный с запросом, в разделе metadata.consumer.
  • Название сервиса под значком metadata.serviceTitle.
  • URL, по которому можно включить сервис, в разделе 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, возникающей, когда запрос превышает лимит на количество запросов mutate в минуту:

{
  "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, чтобы получить ссылки на документацию и локализованные сообщения об ошибках, которые помогут вам понять и устранить проблему.

Например, ниже приведены значения Help и LocalizedMessage для PERMISSION_DENIEDошибки, вызванной использованием учетных данных для облачного проекта Google Cloud, в котором не включен Data Manager API. Полезная нагрузка Help содержит URL, по которому можно включить сервис, а 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.
Реализуйте стратегию повторных попыток

Определите, можно ли повторить попытку, и используйте стратегию повторных попыток.

  • Повторяйте попытки только при временных ошибках сервера (например, UNAVAILABLE, DEADLINE_EXCEEDED, INTERNAL, UNKNOWN и ABORTED) и при ограничении частоты запросов в минуту (RESOURCE_EXHAUSTED с RATE_LIMIT_EXCEEDED).

  • Чтобы узнать об ограничениях частоты запросов, проверьте quota_limit в ErrorInfo:

    • Если ограничение в минуту (например, IngestionMutateRequestsPerMinutePerProject), приостановите запросы и повторите попытку с экспоненциальной выдержкой и случайным отклонением.

    • Если ограничение ежедневное (например, IngestionMutateRequestsPerDayPerProject), не пытайтесь повторить запрос сразу. Приостановите обработку до сброса дневной квоты в полночь по тихоокеанскому времени.

  • Используйте алгоритм экспоненциальной выдержки, чтобы увеличивать интервалы между повторными попытками. Это поможет избежать перегрузки сервиса. Например, подождите 1 секунду, затем 2 секунды, затем 4 секунды и так далее, пока не будет достигнуто максимальное количество попыток или общее время ожидания.

  • Добавьте к задержкам при повторных попытках небольшое случайное значение, чтобы избежать проблемы "громовой толпы", когда многие клиенты пытаются повторно подключиться одновременно.

Подробно регистрируйте данные

Зарегистрируйте полный ответ об ошибке, включая все стандартные полезные нагрузки, особенно идентификатор запроса. Эта информация необходима для отладки и, при необходимости, для отправки отчета о проблеме в службу поддержки Google.

Как оставить отзыв

На основе кодов и сообщений в стандартных полезных нагрузках с подробной информацией предоставляйте пользователям приложения понятные и полезные отзывы. Например, вместо "Произошла ошибка" можно указать "Не указан идентификатор транзакции" или "Не найден идентификатор аккаунта назначения".

Следуя этим рекомендациям, вы сможете эффективно диагностировать и устранять ошибки, возвращаемые Data Manager API, что позволит создавать более стабильные и удобные приложения.