Ведение журналов и мониторинг работают в паре, помогая вам понять и оптимизировать производительность приложений, а также диагностировать ошибки и системные проблемы. Рекомендуется включить ведение сводных журналов для всех вызовов API и подробных журналов для неудачных вызовов 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.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 Ads. Каждый вариант имеет свои недостатки: время реализации, сложность и производительность. Тщательно обдумайте эти недостатки, прежде чем выбирать решение.
Вариант 1: Запись локальных журналов в облако из фонового процесса
Журналы клиентской библиотеки можно записывать в локальный файл на вашем компьютере, изменив конфигурацию ведения журнала. После того, как журналы будут записаны в локальный файл, вы можете настроить демон для сбора журналов и отправки их в облако.
Одним из ограничений этого подхода является то, что некоторые показатели производительности не будут регистрироваться по умолчанию. Журналы клиентской библиотеки содержат информацию из объектов запросов и ответов, поэтому показатели задержки не будут включены, если только не будут внесены дополнительные изменения для их регистрации.
Вариант 2: Запустите приложение на Compute Engine и установите Ops Agent
Если ваше приложение работает на Compute Engine , вы можете отправлять журналы в Google Cloud Logging, установив Ops Agent . Ops Agent можно настроить для отправки журналов приложения в Cloud Logging, в дополнение к метрикам и журналам, отправляемым по умолчанию .
Если ваше приложение уже работает в среде Google Cloud или вы рассматриваете возможность переноса своего приложения в Google Cloud, это отличный вариант для рассмотрения.
Вариант 3: Внедрите ведение журнала в код вашего приложения
Ведение журнала непосредственно из кода приложения может осуществляться одним из двух способов:
Интеграция расчётов метрик и журналов в каждое применимое место кода. Этот вариант более целесообразен для небольших кодовых баз, где объём и затраты на поддержку таких изменений будут минимальными.
Реализация интерфейса журналирования. Если логику приложения можно абстрагировать так, чтобы различные его части наследовали от одного и того же базового класса, логику журналирования можно реализовать в этом базовом классе. Этот вариант, как правило, предпочтительнее включения операторов журналирования в код приложения, поскольку его проще поддерживать и масштабировать. Для больших кодовых баз удобство поддержки и масштабируемость такого решения особенно важны.
Одним из ограничений этого подхода является то, что полные журналы запросов и ответов недоступны из кода приложения. Доступ к полным объектам запросов и ответов возможен из перехватчиков gRPC; именно так встроенная клиентская библиотека ведения журналов получает журналы запросов и ответов. В случае ошибки дополнительная информация может быть доступна в объекте исключения, но для успешных ответов в логике приложения доступно меньше информации. Например, в большинстве случаев идентификатор успешного запроса недоступен из объектов ответа API Google Ads.
Вариант 4: Реализация собственного перехватчика журналирования gRPC
gRPC поддерживает унарные и потоковые перехватчики , которые могут получать доступ к объектам запросов и ответов при их передаче между клиентом и сервером. Клиентские библиотеки API Google Ads используют перехватчики 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