Ведение журнала

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

Ведение журнала клиентской библиотеки

Клиентские библиотеки Google Ads API содержат встроенную систему логирования. Подробную информацию о логировании для конкретной платформы см. в документации по логированию в выбранной вами клиентской библиотеке.

Язык Гид
Java Документация по логированию в Java
.СЕТЬ Документация по логированию для .NET
PHP Документация по логированию в PHP
Python Документация по логированию в Python
Руби Документация по логированию в Ruby
Perl Документация по логированию в Perl

Формат журнала

Клиентские библиотеки Google Ads API генерируют подробный и сводный журналы для каждого вызова API. Подробный журнал содержит все детали вызова API, тогда как сводный журнал содержит минимальные детали вызова API. Пример каждого типа журнала показан ниже, при этом журналы усечены и отформатированы для удобства чтения.

Сводный журнал

GoogleAds.SummaryRequestLogs Warning: 1 : [2023-09-15 19:58:39Z] -
Request made: Host: , Method: /google.ads.googleads.v24.services.GoogleAdsService/SearchStream,
ClientCustomerID: 5951878031, RequestID: hELhBPNlEDd8mWYcZu7b8g,
IsFault: True, FaultMessage: Status(StatusCode="InvalidArgument",
Detail="Request contains an invalid argument.")

Подробный журнал

GoogleAds.DetailedRequestLogs Verbose: 1 : [2023-11-02 21:09:36Z] -
---------------BEGIN API CALL---------------

Request
-------

Method Name: /google.ads.googleads.v24.services.GoogleAdsService/SearchStream
Host:
Headers: {
  "x-goog-api-client": "gl-dotnet/5.0.0 gapic/17.0.1 gax/4.2.0 grpc/2.46.3 gccl/3.0.1 pb/3.21.5",
  "developer-token": "REDACTED",
  "login-customer-id": "1234567890",
  "x-goog-request-params": "customer_id=4567890123"
}

{ "customerId": "4567890123", "query": "SELECT ad_group_criterion.type FROM
  ad_group_criterion WHERE ad_group.status IN(ENABLED, PAUSED) AND
  campaign.status IN(ENABLED, PAUSED) ", "summaryRowSetting": "NO_SUMMARY_ROW" }

Response
--------
Headers: {
  "date": "Thu, 02 Nov 2023 21:09:35 GMT",
  "alt-svc": "h3-29=\":443\"; ma=2592000"
}

{
  "results": [ {
    "adGroupCriterion": {
      "resourceName": "customers/4567890123/adGroupCriteria/456789456789~123456123467",
      "type": "KEYWORD"
    } }, {
    "adGroupCriterion": {
      "resourceName": "customers/4567890123/adGroupCriteria/456789456789~56789056788",
      "type": "KEYWORD"
    } } ],
    "fieldMask": "adGroupCriterion.type", "requestId": "VsJ4F00ew6s9heHvAJ-abw"
}
----------------END API CALL----------------

А что, если я не буду использовать клиентскую библиотеку?

Если вы не используете клиентскую библиотеку, реализуйте собственную систему логирования для сбора подробной информации об исходящих и входящих вызовах API. Вам следует регистрировать как минимум значение заголовка request-id в ответе, которое затем можно будет передавать группам технической поддержки по мере необходимости.

Запись логов в облако

Существует множество инструментов, которые можно использовать для сбора логов и показателей производительности вашего приложения. Например, вы можете использовать Google Cloud Logging для записи показателей производительности в свой проект Google Cloud . Это позволяет настраивать панели мониторинга и оповещения в Google Cloud Monitoring для использования записанных метрик.

Cloud Logging предлагает клиентские библиотеки для всех поддерживаемых языков программирования API Google Ads, кроме Perl, поэтому в большинстве случаев можно вести логи с помощью Cloud Logging непосредственно из интеграции с вашей клиентской библиотекой. Для других языков, включая Perl, Cloud Logging также предлагает REST API .

Существует несколько вариантов для ведения логов в Cloud Logging или другой инструмент из клиентской библиотеки Google Ads API. Каждый вариант имеет свои компромиссы: время внедрения, сложность и производительность. Тщательно обдумайте эти компромиссы, прежде чем принимать решение о выборе подходящего решения.

Вариант 1: Запись локальных логов в облако из фонового процесса.

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

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

Вариант 2: Запустите приложение на Compute Engine и установите Ops Agent.

Если ваше приложение работает на Compute Engine , вы можете отправлять журналы в Google Cloud Logging, установив Ops Agent . Ops Agent можно настроить для отправки журналов вашего приложения в Cloud Logging в дополнение к метрикам и журналам, которые отправляются по умолчанию .

Если ваше приложение уже работает в среде Google Cloud или вы рассматриваете возможность переноса своего приложения в Google Cloud, это отличный вариант для рассмотрения.

Вариант 3: Внедрите логирование в код вашего приложения.

Ведение логов непосредственно из кода приложения может осуществляться двумя способами:

  1. Включение вычислений метрик и логов во все соответствующие места вашего кода. Этот вариант более целесообразен для небольших кодовых баз, где объем и затраты на обслуживание таких изменений будут минимальными.

  2. Реализация интерфейса логирования. Если логику приложения можно абстрагировать таким образом, чтобы различные части приложения наследовали от одного и того же базового класса, логику логирования можно реализовать в этом базовом классе. Этот вариант, как правило, предпочтительнее, чем включение операторов логирования во весь код приложения, поскольку его проще поддерживать и масштабировать. Для больших кодовых баз удобство сопровождения и масштабируемость этого решения становятся еще более важными.

Одним из ограничений этого подхода является недоступность полных журналов запросов и ответов из кода приложения. Доступ к полным объектам запросов и ответов можно получить из перехватчиков gRPC; именно так встроенная библиотека логирования клиента получает журналы запросов и ответов. В случае ошибки дополнительная информация может быть доступна в объекте исключения, но для успешных ответов в рамках логики приложения доступно меньше подробностей. Например, в большинстве случаев идентификатор запроса для успешного запроса недоступен из объектов ответов API Google Ads.

Вариант 4: Реализация собственного перехватчика логов gRPC.

gRPC поддерживает унарные и потоковые перехватчики , которые могут получать доступ к объектам запроса и ответа по мере их передачи между клиентом и сервером. Клиентские библиотеки Google Ads API используют перехватчики gRPC для обеспечения встроенной поддержки логирования. Аналогичным образом, вы можете реализовать собственный перехватчик gRPC для доступа к объектам запроса и ответа, извлечения информации для целей логирования и мониторинга и записи этих данных в выбранное вами место.

В отличие от некоторых других представленных здесь решений, реализация пользовательского gRPC-перехватчика обеспечивает гибкость в перехвате объектов запроса и ответа для каждого запроса, а также позволяет реализовать дополнительную логику для сбора подробной информации о запросе. Например, вы можете рассчитать прошедшее время запроса, реализовав логику измерения производительности в самом пользовательском перехватчике, а затем записать этот показатель в Google Cloud Logging, чтобы сделать его доступным для мониторинга задержки в Google Cloud Monitoring.

Пользовательский перехватчик Google Cloud Logging на Python

Для демонстрации этого решения мы написали пример пользовательского перехватчика логов на Python. Пользовательский перехватчик создается и передается в клиент сервиса. Затем он обращается к объектам запроса и ответа, которые передаются при каждом вызове метода сервиса, обрабатывает данные из этих объектов и отправляет данные в Google Cloud Logging.

В дополнение к данным, получаемым из объектов запроса и ответа, в примере реализована дополнительная логика для фиксации прошедшего времени запроса, а также некоторые другие метаданные, полезные для целей мониторинга, например, был ли запрос успешным или нет. Более подробную информацию о том, как эта информация может быть полезна как для мониторинга в целом, так и при использовании Google Cloud Logging и Google Cloud Monitoring, см. в руководстве по мониторингу .

# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A custom gRPC Interceptor that logs requests and responses to Cloud Logging.

The custom interceptor object is passed into the get_service method of the
GoogleAdsClient. It intercepts requests and responses, parses them into a
human readable structure and logs them using the logging service instantiated
within the class (in this case, a Cloud Logging client).
"""

import time
from typing import Any, Callable, Dict, Optional

from google.cloud import logging as google_cloud_logging
from grpc._interceptor import _ClientCallDetails

from google.ads.googleads.interceptors import LoggingInterceptor


class CloudLoggingInterceptor(LoggingInterceptor):
    """An interceptor that logs rpc request and response details to Google Cloud Logging.

    This class inherits logic from the LoggingInterceptor, which simplifies the
    implementation here. Some logic is required here in order to make the
    underlying logic work -- comments make note of this where applicable.
    NOTE: Inheriting from the LoggingInterceptor class could yield unexpected side
    effects. For example, if the LoggingInterceptor class is updated, this class would
    inherit the updated logic, which could affect its functionality. One option to avoid
    this is to inherit from the Interceptor class instead, and selectively copy whatever
    logic is needed from the LoggingInterceptor class."""

    def __init__(self, api_version: str):
        """Initializer for the CloudLoggingInterceptor.

        Args:
            api_version: a str of the API version of the request.
        """
        super().__init__(logger=None, api_version=api_version)
        # Instantiate the Cloud Logging client.
        logging_client: google_cloud_logging.Client = google_cloud_logging.Client()
        self.logger: google_cloud_logging.Logger = logging_client.logger("cloud_logging")
        self.rpc_start: float
        self.rpc_end: float

    def log_successful_request(
        self,
        method: str,
        customer_id: Optional[str],
        metadata_json: str,
        request_id: str,
        request: Any,  # google.ads.googleads.vX.services.types.SearchGoogleAdsRequest or SearchGoogleAdsStreamRequest
        trailing_metadata_json: str,
        response: Any,  # grpc.Call or grpc.Future
    ) -> None:
        """Handles logging of a successful request.

        Args:
            method: The method of the request.
            customer_id: The customer ID associated with the request.
            metadata_json: A JSON str of initial_metadata.
            request_id: A unique ID for the request provided in the response.
            request: An instance of a request proto message.
            trailing_metadata_json: A JSON str of trailing_metadata.
            response: A grpc.Call/grpc.Future instance.
        """
        # Retrieve and mask the RPC result from the response future.
        # This method is available from the LoggingInterceptor class.
        # Ensure self._cache is set in order for this to work.
        # The response result could contain up to 10,000 rows of data,
        # so consider truncating this value before logging it, to save
        # on data storage costs and maintain readability.
        result: Any = self.retrieve_and_mask_result(response)

        # elapsed_ms is the approximate elapsed time of the RPC, in milliseconds.
        # There are different ways to define and measure elapsed time, so use
        # whatever approach makes sense for your monitoring purposes.
        # rpc_start and rpc_end are set in the intercept_unary_* methods below.
        elapsed_ms: float = (self.rpc_end - self.rpc_start) * 1000

        debug_log: Dict[str, Any] = {
            "method": method,
            "host": metadata_json,
            "request_id": request_id,
            "request": str(request),
            "headers": trailing_metadata_json,
            "response": str(result),
            "is_fault": False,
            "elapsed_ms": elapsed_ms,
        }
        self.logger.log_struct(debug_log, severity="DEBUG")

        info_log: Dict[str, Any] = {
            "customer_id": customer_id,
            "method": method,
            "request_id": request_id,
            "is_fault": False,
            # Available from the Interceptor class.
            "api_version": self._api_version,
        }
        self.logger.log_struct(info_log, severity="INFO")

    def log_failed_request(
        self,
        method: str,
        customer_id: Optional[str],
        metadata_json: str,
        request_id: str,
        request: Any,  # google.ads.googleads.vX.services.types.SearchGoogleAdsRequest or SearchGoogleAdsStreamRequest
        trailing_metadata_json: str,
        response: Any,  # grpc.Call or grpc.Future
    ) -> None:
        """Handles logging of a failed request.

        Args:
            method: The method of the request.
            customer_id: The customer ID associated with the request.
            metadata_json: A JSON str of initial_metadata.
            request_id: A unique ID for the request provided in the response.
            request: An instance of a request proto message.
            trailing_metadata_json: A JSON str of trailing_metadata.
            response: A JSON str of the response message.
        """
        exception: Any = self._get_error_from_response(response)
        exception_str: str = self._parse_exception_to_str(exception)
        fault_message: str = self._get_fault_message(exception)

        info_log: Dict[str, Any] = {
            "method": method,
            "endpoint": self.endpoint,
            "host": metadata_json,
            "request_id": request_id,
            "request": str(request),
            "headers": trailing_metadata_json,
            "exception": exception_str,
            "is_fault": True,
        }
        self.logger.log_struct(info_log, severity="INFO")

        error_log: Dict[str, Any] = {
            "method": method,
            "endpoint": self.endpoint,
            "request_id": request_id,
            "customer_id": customer_id,
            "is_fault": True,
            "fault_message": fault_message,
        }
        self.logger.log_struct(error_log, severity="ERROR")

    def intercept_unary_unary(
        self,
        continuation: Callable[[_ClientCallDetails, Any], Any], # Any is request type
        client_call_details: _ClientCallDetails,
        request: Any,  # google.ads.googleads.vX.services.types.SearchGoogleAdsRequest
    ) -> Any:  # grpc.Call or grpc.Future
        """Intercepts and logs API interactions.

        Overrides abstract method defined in grpc.UnaryUnaryClientInterceptor.

        Args:
            continuation: a function to continue the request process.
            client_call_details: a grpc._interceptor._ClientCallDetails
                instance containing request metadata.
            request: a SearchGoogleAdsRequest or SearchGoogleAdsStreamRequest
                message class instance.

        Returns:
            A grpc.Call/grpc.Future instance representing a service response.
        """
        # Set the rpc_end value to current time when RPC completes.
        def update_rpc_end(response_future: Any) -> None: # response_future is grpc.Future
            self.rpc_end = time.perf_counter()

        # Capture precise clock time to later calculate approximate elapsed
        # time of the RPC.
        self.rpc_start = time.perf_counter()

        # The below call is REQUIRED.
        response: Any = continuation(client_call_details, request) # response is grpc.Call or grpc.Future

        response.add_done_callback(update_rpc_end)

        self.log_request(client_call_details, request, response)

        # The below return is REQUIRED.
        return response

    def intercept_unary_stream(
        self,
        continuation: Callable[[_ClientCallDetails, Any], Any], # Any is request type
        client_call_details: _ClientCallDetails,
        request: Any,  # google.ads.googleads.vX.services.types.SearchGoogleAdsStreamRequest
    ) -> Any:  # grpc.Call or grpc.Future
        """Intercepts and logs API interactions for Unary-Stream requests.

        Overrides abstract method defined in grpc.UnaryStreamClientInterceptor.

        Args:
            continuation: a function to continue the request process.
            client_call_details: a grpc._interceptor._ClientCallDetails
                instance containing request metadata.
            request: a SearchGoogleAdsRequest or SearchGoogleAdsStreamRequest
                message class instance.

        Returns:
            A grpc.Call/grpc.Future instance representing a service response.
        """

        def on_rpc_complete(response_future: Any) -> None: # response_future is grpc.Future
            self.rpc_end = time.perf_counter()
            self.log_request(client_call_details, request, response_future)

        # Capture precise clock time to later calculate approximate elapsed
        # time of the RPC.
        self.rpc_start = time.perf_counter()

        # The below call is REQUIRED.
        response: Any = continuation(client_call_details, request) # response is grpc.Call or grpc.Future

        # Set self._cache to the cache on the response wrapper in order to
        # access the streaming logs. This is REQUIRED in order to log streaming
        # requests.
        self._cache = response.get_cache()

        response.add_done_callback(on_rpc_complete)

        # The below return is REQUIRED.
        return response