O registro e o monitoramento funcionam em conjunto para ajudar você a entender e otimizar o desempenho do aplicativo, além de diagnosticar erros e problemas relacionados ao sistema. Ative os registros de resumo para todas as chamadas de API e os registros detalhados para chamadas de API com falha. Assim, você pode fornecer os registros de chamadas de API quando precisar de suporte técnico.
Registro da biblioteca de cliente
As bibliotecas de cliente da API Google Ads vêm com registro integrado. Para detalhes de registro específicos da plataforma, consulte a documentação de registro na biblioteca de cliente de sua escolha.
Formato do registro
As bibliotecas de cliente da API Google Ads geram um registro detalhado e um registro de resumo para cada chamada de API. O registro detalhado contém todos os detalhes da chamada de API, enquanto o registro de resumo contém detalhes mínimos da chamada de API. Um exemplo de cada tipo de registro é mostrado, com os registros truncados e formatados para facilitar a leitura.
Registro de resumo
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.")
Registro detalhado
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----------------
E se eu não usar uma biblioteca de cliente?
Se você não usa uma biblioteca de cliente, implemente seu próprio registro para capturar os detalhes das chamadas de API de saída e entrada. Registre pelo menos o
valor do request-id cabeçalho de resposta, que pode ser compartilhado com as
equipes de suporte técnico conforme necessário.
Registro na nuvem
Há muitas ferramentas que podem ser usadas para capturar registros e métricas de desempenho do seu aplicativo. Por exemplo, você pode usar Cloud Logging para registrar métricas de desempenho no seu projeto do Google Cloud. Isso permite configurar painéis e alertas no Google Cloud Monitoring para usar as métricas registradas.
O Cloud Logging oferece bibliotecas de cliente para todas as linguagens de biblioteca de cliente da API Google Ads com suporte, exceto Perl. Portanto, na maioria dos casos, é possível registrar com o Cloud Logging diretamente da integração da biblioteca de cliente. Para outras linguagens incluindo Perl, o Cloud Logging também oferece uma API REST.
Há algumas opções para registrar no Cloud Logging ou em outra ferramenta de uma biblioteca de cliente da API Google Ads. Cada opção tem suas próprias compensações de tempo de implementação, complexidade e desempenho. Pense com cuidado nessas compensações antes de decidir qual solução implementar.
Opção 1: gravar registros locais na nuvem de um processo em segundo plano
Os registros da biblioteca de cliente podem ser gravados em um arquivo local na sua máquina modificando a configuração de registro. Depois que os registros são enviados para um arquivo local, você pode configurar um daemon para coletar os registros e enviá-los para a nuvem.
Uma limitação dessa abordagem é que algumas métricas de desempenho não serão capturadas por padrão. Os registros da biblioteca de cliente incluem detalhes dos objetos de solicitação e resposta. Portanto, as métricas de latência não serão incluídas, a menos que outras mudanças sejam feitas para registrar esses dados também.
Opção 2: executar o aplicativo no Compute Engine e instalar o Agente de operações
Se o aplicativo estiver em execução no Compute Engine, você poderá enviar os registros para o Google Cloud Logging instalando o Agente de operações. O Agente de operações pode ser configurado para enviar os registros do aplicativo para o Cloud Logging, além das métricas e registros enviados por padrão.
Se o aplicativo já estiver em execução em um ambiente do Google Cloud ou se você estiver considerando migrar o aplicativo para o Google Cloud, essa é uma ótima opção.
Opção 3: implementar o registro no código do aplicativo
O registro diretamente do código do aplicativo pode ser feito de duas maneiras:
Incorporar cálculos de métricas e instruções de registro em todos os locais aplicáveis no código. Essa opção é mais viável para bases de código menores, em que o escopo e os custos de manutenção de uma mudança desse tipo seriam mínimos.
Implementar uma interface de registro. Se a lógica do aplicativo puder ser abstraída para que diferentes partes do aplicativo herdem da mesma classe de base, a lógica de geração de registros poderá ser implementada nessa classe de base. Essa opção geralmente é preferível à incorporação de instruções de registro em todo o código do aplicativo, porque é mais fácil de manter e escalonar. Para bases de código maiores, a capacidade de manutenção e escalonabilidade dessa solução são ainda mais relevantes.
Uma limitação dessa abordagem é que os registros completos de solicitação e resposta não estão disponíveis no código do aplicativo. Os objetos de solicitação e resposta completos podem ser acessados nos interceptores gRPC. É assim que o registro da biblioteca de cliente integrada recebe registros de solicitação e resposta. Em caso de erro, mais informações podem estar disponíveis no objeto de exceção, mas menos detalhes estão disponíveis para respostas bem-sucedidas na lógica do aplicativo. Por exemplo, na maioria dos casos, o ID da solicitação de uma solicitação bem-sucedida não pode ser acessado nos objetos de resposta da API Google Ads.
Opção 4: implementar um interceptor de registro gRPC personalizado
O gRPC oferece suporte a interceptores unários e de streaming que podem acessar os objetos de solicitação e resposta à medida que eles passam entre o cliente e o servidor. As bibliotecas de cliente da API Google Ads usam interceptores gRPC para oferecer suporte de registro integrado. Da mesma forma, você pode implementar um interceptor gRPC personalizado para acessar os objetos de solicitação e resposta, extrair informações para fins de registro e monitoramento e gravar esses dados no local de sua escolha.
Ao contrário de algumas das outras soluções apresentadas aqui, a implementação de um interceptor gRPC personalizado oferece flexibilidade para capturar objetos de solicitação e resposta em cada solicitação e implementar uma lógica adicional para capturar detalhes da solicitação. Por exemplo, é possível calcular o tempo decorrido de uma solicitação implementando a lógica de tempo de desempenho no próprio interceptor personalizado e, em seguida, registrar a métrica no Cloud Logging para disponibilizá-la para monitoramento de latência no Cloud Monitoring.
Interceptor personalizado do Cloud Logging do Google Cloud em Python
Para demonstrar essa solução, escrevemos um exemplo de um interceptor de registro personalizado em Python. O interceptor personalizado é criado e transmitido para o cliente de serviço. Em seguida, ele acessa os objetos de solicitação e resposta que passam por cada chamada de método de serviço, processa dados desses objetos e envia os dados para o Cloud Logging.
Além dos dados que vêm dos objetos de solicitação e resposta, o exemplo implementa uma lógica adicional para capturar o tempo decorrido da solicitação e alguns outros metadados que seriam úteis para fins de monitoramento, como se a solicitação foi bem-sucedida ou não. Para mais informações sobre como essas informações podem ser úteis, tanto para monitoramento em geral quanto especificamente ao combinar o Cloud Logging e o Cloud Monitoring, consulte o guia de monitoramento.
# 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