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

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

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

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

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

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

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

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

GoogleAds.SummaryRequestLogs Warning: 1 : [2023-09-15 19:58:39Z] -
Request made: Host: , Method: /google.ads.googleads.v14.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.v14.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 предлагает клиентские библиотеки для всех поддерживаемых языков клиентских библиотек Google Ads API, за исключением Perl, поэтому в большинстве случаев можно войти в систему с помощью Cloud Logging непосредственно из интеграции вашей клиентской библиотеки. Для других языков, включая Perl, Cloud Logging также предлагает REST API .

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

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

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

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

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

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

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

Вариант 3. Реализуйте вход в код приложения.

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

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

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

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

Вариант 4. Внедрение специального перехватчика журналирования gRPC.

gRPC поддерживает унарные и потоковые перехватчики , которые могут получать доступ к объектам запроса и ответа при их передаче между клиентом и сервером. Клиентские библиотеки API Google Рекламы используют перехватчики 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 logging
import time

from google.cloud import logging
from grpc import UnaryUnaryClientInterceptor, UnaryStreamClientInterceptor

from google.ads.googleads.interceptors import LoggingInterceptor, mask_message


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):
        """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 = logging.Client()
        self.logger = logging_client.logger("cloud_logging")

    def log_successful_request(
        self,
        method,
        customer_id,
        metadata_json,
        request_id,
        request,
        trailing_metadata_json,
        response,
    ):
        """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 = 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 = (self.rpc_end - self.rpc_start) * 1000

        debug_log = {
            "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 = {
            "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,
        customer_id,
        metadata_json,
        request_id,
        request,
        trailing_metadata_json,
        response,
    ):
        """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 = self._get_error_from_response(response)
        exception_str = self._parse_exception_to_str(exception)
        fault_message = self._get_fault_message(exception)

        info_log = {
            "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 = {
            "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, client_call_details, request):
        """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):
            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 = continuation(client_call_details, request)

        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, client_call_details, request
    ):
        """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):
            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 = continuation(client_call_details, request)

        # 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