Logowanie i monitorowanie działają razem, aby pomóc Ci zrozumieć i zoptymalizować wydajność aplikacji, a także diagnozować błędy i problemy związane z systemem. Włącz dzienniki podsumowania dla wszystkich wywołań interfejsu API i dzienniki szczegółowe dla nieudanych wywołań interfejsu API, aby móc dostarczyć dzienniki wywołań interfejsu API, gdy będziesz potrzebować pomocy technicznej.
Logowanie biblioteki klienta
Biblioteki klienta interfejsu Google Ads API mają wbudowane logowanie. Szczegółowe informacje o rejestrowaniu na poszczególnych platformach znajdziesz w dokumentacji dotyczącej rejestrowania w wybranej bibliotece klienta.
| Język | Przewodnik |
|---|---|
| Java | Dokumentacja usługi Logging dla Javy |
| .NET | Dokumentacja Logging dla .NET |
| PHP | Dokumentacja usługi Logging dla PHP |
| Python | Dokumentacja usługi Logging dla Pythona |
| Ruby | Dokumentacja Logging dla Ruby |
| Perl | Dokumentacja Logging dla Perla |
Format logu
Biblioteki klienta interfejsu Google Ads API generują szczegółowy dziennik i dziennik podsumowania dla każdego wywołania interfejsu API. Log szczegółowy zawiera wszystkie szczegóły wywołania interfejsu API, a log podsumowujący – tylko minimalne szczegóły wywołania interfejsu API. Poniżej znajdziesz przykłady każdego typu dziennika. Zostały one skrócone i sformatowane, aby były bardziej czytelne.
Log podsumowania
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.")
Szczegółowy dziennik
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----------------
Co zrobić, jeśli nie używam biblioteki klienta?
Jeśli nie używasz biblioteki klienta, zaimplementuj własne logowanie, aby rejestrować szczegóły wychodzących i przychodzących wywołań interfejsu API. Należy zarejestrować co najmniej wartość nagłówka odpowiedzi request-id, którą można następnie udostępnić zespołom pomocy technicznej.
Logowanie w chmurze
Do przechwytywania logów i wskaźników skuteczności aplikacji możesz używać wielu narzędzi. Możesz na przykład użyć Google Cloud Logging, aby rejestrować wskaźniki wydajności w projekcie Google Cloud. Dzięki temu możesz skonfigurować panele i alerty w Google Cloud Monitoring, aby korzystać ze wskaźników logów.
Cloud Logging udostępnia biblioteki klienta we wszystkich językach bibliotek klienta interfejsu Google Ads API oprócz Perla, więc w większości przypadków możesz rejestrować dane w Cloud Logging bezpośrednio z integracji biblioteki klienta. W przypadku innych języków, w tym Perla, Cloud Logging oferuje również interfejs API typu REST.
Istnieje kilka opcji logowania się w Cloud Logging lub innym narzędziu z biblioteki klienta interfejsu Google Ads API. Każda opcja ma swoje zalety i wady związane z czasem wdrożenia, złożonością i wydajnością. Przed podjęciem decyzji o wdrożeniu rozwiązania dokładnie rozważ te zalety i wady.
Opcja 1. Zapisywanie dzienników lokalnych z procesu w tle
Logi biblioteki klienta można zapisywać w pliku lokalnym na komputerze, modyfikując konfigurację logowania. Po zapisaniu logów w pliku lokalnym możesz skonfigurować demona, który będzie zbierać logi i wysyłać je do chmury.
Jednym z ograniczeń tego podejścia jest to, że niektóre wskaźniki skuteczności nie będą domyślnie rejestrowane. Logi biblioteki klienta zawierają szczegóły z obiektów żądania i odpowiedzi, więc dane o opóźnieniu nie będą uwzględniane, chyba że wprowadzisz dodatkowe zmiany, aby je również rejestrować.
Opcja 2. Użycie Compute Engine i agenta operacyjnego
Jeśli aplikacja działa w Compute Engine, możesz wysyłać logi do Google Cloud Logging, instalując agenta operacyjnego. Agenta operacyjnego można skonfigurować tak, aby oprócz wskaźników i logów wysyłanych domyślnie wysyłał do Cloud Logging logi aplikacji.
Jeśli Twoja aplikacja działa już w środowisku Google Cloud lub rozważasz przeniesienie jej do Google Cloud, warto rozważyć tę opcję.
Opcja 3. Implementowanie logowania w kodzie aplikacji
Logowanie bezpośrednio z kodu aplikacji można wykonać na 2 sposoby:
uwzględnianie obliczeń danych i instrukcji logowania w każdym odpowiednim miejscu w kodzie; Ta opcja jest bardziej wykonalna w przypadku mniejszych baz kodu, gdzie zakres i koszty utrzymania takiej zmiany byłyby minimalne.
implementowanie interfejsu logowania, Jeśli logikę aplikacji można wyabstrahować, tak aby różne jej części dziedziczyły z tej samej klasy bazowej, logikę logowania można zaimplementować w tej klasie bazowej. Ta opcja jest zwykle preferowana w porównaniu z umieszczaniem instrukcji logowania w kodzie aplikacji, ponieważ jest łatwiejsza w utrzymaniu i skalowaniu. W przypadku większych baz kodu łatwość konserwacji i skalowalność tego rozwiązania są jeszcze ważniejsze.
Jednym z ograniczeń tego podejścia jest to, że pełne dzienniki żądań i odpowiedzi nie są dostępne z poziomu kodu aplikacji. Dostęp do pełnych obiektów żądań i odpowiedzi można uzyskać z przechwytywaczy gRPC. W ten sposób wbudowane logowanie biblioteki klienta uzyskuje logi żądań i odpowiedzi. W przypadku błędu dodatkowe informacje mogą być dostępne w obiekcie wyjątku, ale w logice aplikacji jest dostępnych mniej szczegółów dotyczących udanych odpowiedzi. Na przykład w większości przypadków identyfikator żądania, które zostało zrealizowane, jest niedostępny w obiektach odpowiedzi interfejsu Google Ads API.
Opcja 4. Niestandardowy przechwytywacz logowania gRPC
gRPC obsługuje jednoargumentowe i strumieniowe interceptory, które mogą uzyskiwać dostęp do obiektów żądań i odpowiedzi, gdy są one przesyłane między klientem a serwerem. Biblioteki klienta interfejsu Google Ads API używają przechwytywaczy gRPC, aby zapewnić wbudowaną obsługę logowania. Podobnie możesz wdrożyć niestandardowy interceptor gRPC, aby uzyskać dostęp do obiektów żądania i odpowiedzi, wyodrębnić informacje na potrzeby logowania i monitorowania oraz zapisać te dane w wybranej lokalizacji.
W przeciwieństwie do niektórych innych rozwiązań przedstawionych tutaj implementacja niestandardowego przechwytywacza gRPC zapewnia elastyczność przechwytywania obiektów żądania i odpowiedzi przy każdym żądaniu oraz implementowania dodatkowej logiki w celu przechwytywania szczegółów żądania. Możesz na przykład obliczyć czas, jaki upłynął od wysłania żądania, implementując logikę pomiaru czasu w niestandardowym przechwytywaczu, a następnie zarejestrować dane w Google Cloud Logging, aby były dostępne do monitorowania opóźnień w Google Cloud Monitoring.
Niestandardowy przechwytywacz Cloud Logging w Pythonie
Aby zademonstrować to rozwiązanie, napisaliśmy przykładowy niestandardowy interceptor logowania w języku Python. Niestandardowy interceptor jest tworzony i przekazywany do klienta usługi. Następnie uzyskuje dostęp do obiektów żądania i odpowiedzi, które przechodzą przez każde wywołanie metody usługi, przetwarza dane z tych obiektów i wysyła je do Google Cloud Logging.
Oprócz danych pochodzących z obiektów żądania i odpowiedzi przykład implementuje dodatkową logikę, która pozwala rejestrować czas, jaki upłynął od żądania, oraz inne metadane, które mogą być przydatne do celów monitorowania, np. czy żądanie zostało zrealizowane. Więcej informacji o tym, jak te informacje mogą być przydatne zarówno ogólnie do monitorowania, jak i konkretnie podczas łączenia Google Cloud Logging i Google Cloud Monitoring, znajdziesz w przewodniku po monitorowaniu.
# 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