記錄

記錄和監控功能相輔相成,可協助您瞭解及最佳化應用程式效能,以及診斷錯誤和系統相關問題。建議您為所有 API 呼叫啟用摘要記錄,並為失敗的 API 呼叫啟用詳細記錄,以便在需要技術支援時提供 API 呼叫記錄。

用戶端程式庫記錄

Google Ads API 用戶端程式庫內建記錄功能。如需特定平台的記錄詳細資料,請參閱所選用戶端程式庫中的記錄說明文件。

語言 指南
Java Java 適用的 Logging 說明文件
.NET .NET 的 Logging 說明文件
PHP PHP 的 Logging 說明文件
Python Python 適用的 Logging 說明文件
小茹 Ruby 適用的 Logging 文件
Perl Perl 的 Logging 說明文件

記錄格式

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:在應用程式程式碼中實作記錄功能

直接從應用程式程式碼記錄的方式有兩種:

  1. 在程式碼中所有適用位置加入指標計算和記錄陳述式。這個選項較適合小型程式碼集,因為這類變更的範圍和維護成本極低。

  2. 實作記錄介面。如果應用程式邏輯可以抽象化,讓應用程式的不同部分繼承自同一個基礎類別,則可以在該基礎類別中實作記錄邏輯。這個選項通常比在整個應用程式程式碼中加入記錄陳述式更受歡迎,因為更容易維護和擴充。對於較大的程式碼集,這項解決方案的可維護性和擴充性更為重要。

這種做法的限制之一,是無法從應用程式程式碼取得完整的要求和回應記錄。您可以透過 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