記錄和監控功能相輔相成,可協助您瞭解及最佳化應用程式效能,以及診斷錯誤和系統相關問題。您應為所有 API 呼叫啟用摘要記錄,並為失敗的 API 呼叫啟用詳細記錄,以便在需要技術支援時提供 API 呼叫記錄。
用戶端程式庫記錄
Google Ads API 用戶端程式庫內建記錄功能。如需特定平台的記錄詳細資料,請參閱所選用戶端程式庫中的記錄說明文件。
| 語言 | 指南 |
|---|---|
| Java | Java Logging 說明文件 |
| .NET | .NET 的記錄文件 |
| PHP | PHP 適用的記錄檔文件 |
| Python | Python 適用的 Logging 說明文件 |
| 小茹 | Ruby 適用的記錄文件 |
| Perl | Perl 的記錄文件 |
記錄格式
Google Ads API 用戶端程式庫會為每個 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 回應標頭的值,然後視需要與技術支援團隊分享。
記錄至雲端
您可以使用多種工具擷取應用程式的記錄和成效指標。舉例來說,您可以使用 Cloud Logging,將成效指標記錄到 雲端專案。因此,您可以在 Google Cloud Monitoring 中設定資訊主頁和警告,以使用記錄指標。
Cloud Logging 提供所有支援的 Google Ads API 用戶端程式庫語言 (Perl 除外) 的用戶端程式庫,因此在大多數情況下,您可以直接從用戶端程式庫整合記錄 Cloud Logging。Cloud Logging 也提供 REST API,適用於 Perl 等其他語言。
您可以透過幾種方式,從 Google Ads API 用戶端程式庫記錄到 Cloud Logging 或其他工具。每種選項在實作時間、複雜度和效能方面各有優缺點。決定要導入哪種解決方案前,請仔細思考這些取捨條件。
方法 1:從背景程序寫入本機記錄
您可以修改記錄設定,將用戶端程式庫記錄寫入電腦上的本機檔案。將記錄輸出至本機檔案後,您可以設定 Daemon 來收集記錄並傳送至雲端。
這種做法的限制之一,是系統預設不會擷取部分成效指標。用戶端程式庫記錄包含要求和回應物件的詳細資料,因此除非對記錄進行額外變更,否則不會納入延遲指標。
方法 2:使用 Compute Engine 和 Ops Agent
如果應用程式是在 Compute Engine 上執行,您可以安裝 Ops Agent,將記錄檔傳送至 Google Cloud Logging。除了預設傳送的指標和記錄,您可以設定 Ops Agent,將應用程式記錄傳送至 Cloud Logging。
如果您的應用程式已在 Google Cloud 環境中執行,或您考慮將應用程式移至 Google Cloud,這是不錯的選擇。
選項 3:在應用程式程式碼中導入記錄功能
直接從應用程式程式碼記錄的方式有兩種:
在程式碼中每個適用的位置納入指標計算和記錄陳述式。這個選項較適合小型程式碼集,因為這類變更的範圍和維護成本極低。
導入記錄介面。如果應用程式邏輯可以抽象化,讓應用程式的不同部分繼承自同一個基礎類別,則可以在該基礎類別中實作記錄邏輯。這個選項通常比在整個應用程式程式碼中加入記錄陳述式更合適,因為更容易維護及擴充。對於較大的程式碼集,這個解決方案的可維護性和可擴充性就更顯重要。
這種做法的限制之一是,應用程式程式碼無法提供完整的要求和回應記錄。可從 gRPC 攔截器存取完整的要求和回應物件;內建的用戶端程式庫記錄檔就是透過這種方式取得要求和回應記錄。發生錯誤時,例外狀況物件中可能會提供額外資訊,但應用程式邏輯中的成功回應提供的詳細資料較少。舉例來說,在多數情況下,您無法從 Google Ads API 回應物件存取成功要求的 ID。
方法 4:自訂 gRPC 記錄攔截器
gRPC 支援一元和串流攔截器,可在要求和回應物件在用戶端和伺服器之間傳遞時存取這些物件。Google Ads API 用戶端程式庫使用 gRPC 攔截器,提供內建記錄支援。同樣地,您也可以實作自訂 gRPC 攔截器,存取要求和回應物件、擷取資訊以供記錄和監控,並將資料寫入您選擇的位置。
與這裡介紹的其他解決方案不同,實作自訂 gRPC 攔截器可讓您彈性擷取每個要求的請求和回應物件,並實作額外邏輯來擷取要求的詳細資料。舉例來說,您可以在自訂攔截器中實作效能計時邏輯,計算要求的經過時間,然後將指標記錄到 Google Cloud Logging,以便在 Google Cloud Monitoring 中監控延遲時間。
Python 中的自訂 Cloud Logging 攔截器
為示範這項解決方案,我們以 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