Ведение журналов и мониторинг работают в тандеме, помогая вам понять и оптимизировать производительность приложений, а также диагностировать ошибки и проблемы, связанные с системой. Рекомендуем включить сводные журналы для всех вызовов API и подробные журналы для неудачных вызовов API, чтобы вы могли предоставить журналы вызовов API при обращении в службу технической поддержки.
Ведение журнала клиентской библиотеки
Клиентские библиотеки Google Ads API имеют встроенную функцию ведения журналов. Подробные сведения о ведении журналов на разных платформах можно найти в документации по ведению журналов в выбранной вами клиентской библиотеке.
Формат журнала
Клиентские библиотеки Google Ads API создают подробный журнал и сводный журнал для каждого вызова API. Подробный журнал содержит все сведения о вызове API, а сводный – только основные. Ниже приведены примеры каждого типа журналов. Для удобства чтения они сокращены и отформатированы.
Сводный журнал
GoogleAds.SummaryRequestLogs Warning: 1 : [2023-09-15 19:58:39Z] -
Request made: Host: ,
Method: /google.ads.googleads.v26.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.v26.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",
"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 или другом инструменте с помощью клиентской библиотеки Google Ads API. У каждого варианта есть свои плюсы и минусы, связанные со временем реализации, сложностью и эффективностью. Тщательно обдумайте эти компромиссы, прежде чем решать, какое решение реализовать.
Вариант 1. Запишите локальные журналы из фонового процесса
Чтобы журналы клиентской библиотеки записывались в локальный файл на вашем компьютере, измените конфигурацию ведения журнала. После того как журналы будут выведены в локальный файл, вы можете настроить демон для сбора журналов и отправки их в облако.
Ограничение этого подхода заключается в том, что некоторые показатели эффективности не будут регистрироваться по умолчанию. Журналы клиентской библиотеки содержат сведения об объектах запроса и ответа, поэтому показатели задержки не будут включены, если не внести дополнительные изменения для их регистрации.
Вариант 2. Используйте Compute Engine и агент Ops
Если ваше приложение работает в Compute Engine, вы можете отправлять журналы в Google Cloud Logging, установив агента Ops. Агент Ops Agent можно настроить так, чтобы он отправлял журналы приложений в Cloud Logging в дополнение к показателям и журналам, которые отправляются по умолчанию.
Если ваше приложение уже работает в среде Google Cloud или вы планируете перенести его туда, это отличный вариант.
Вариант 3. Реализуйте ведение журналов в коде приложения
Вести журнал непосредственно из кода приложения можно одним из двух способов:
Включите расчет показателей и операторы журнала во все подходящие места в коде. Этот вариант больше подходит для небольших баз кода, где объем и стоимость поддержки таких изменений будут минимальными.
Реализация интерфейса ведения журнала. Если логику приложения можно абстрагировать так, чтобы разные его части наследовали один и тот же базовый класс, логику ведения журнала можно реализовать в этом базовом классе. Этот вариант обычно предпочтительнее, чем добавление операторов журнала в код приложения, поскольку его проще поддерживать и масштабировать. Для более крупных баз кода простота обслуживания и масштабируемость этого решения ещё более актуальны.
Ограничение этого подхода заключается в том, что полные журналы запросов и ответов недоступны из кода приложения. Полные объекты запросов и ответов можно получить с помощью перехватчиков gRPC. Именно так встроенная в клиентскую библиотеку функция ведения журналов получает журналы запросов и ответов. В случае ошибки в объекте исключения может быть доступна дополнительная информация, но в логике приложения для успешных ответов доступно меньше деталей. Например, в большинстве случаев идентификатор запроса для успешного запроса недоступен из объектов ответа Google Ads API.
Вариант 4. Специальный перехватчик ведения журналов gRPC
gRPC поддерживает унарные и потоковые перехватчики, которые могут получать доступ к объектам запроса и ответа при их передаче между клиентом и сервером. Клиентские библиотеки Google Ads API используют перехватчики gRPC для встроенной поддержки ведения журналов. Аналогичным образом можно реализовать собственный перехватчик gRPC для доступа к объектам запроса и ответа, извлечения информации для ведения журналов и мониторинга, а также записи этих данных в выбранное место.
В отличие от некоторых других решений, представленных здесь, реализация пользовательского перехватчика gRPC дает вам возможность захватывать объекты запроса и ответа при каждом запросе и реализовывать дополнительную логику для захвата деталей запроса. Например, вы можете рассчитать время, прошедшее с момента отправки запроса, реализовав логику измерения производительности в самом пользовательском перехватчике, а затем зарегистрировать показатель в Google Cloud Logging, чтобы сделать его доступным для мониторинга задержки в Google Cloud Monitoring.
Специальный перехватчик 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