記錄

記錄和監控功能可同時運作,協助您瞭解及改善應用程式效能,以及診斷錯誤和系統相關問題。您應為所有 API 呼叫啟用摘要記錄,並為失敗的 API 呼叫啟用詳細記錄,這樣在需要技術支援時,您就能提供 API 呼叫記錄。

用戶端程式庫記錄

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

語言 指南
Java Java 適用的 Logging 說明文件
.NET .NET 的記錄說明文件
PHP PHP 適用的 Logging 說明文件
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.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

您可以透過幾種方式,從 Google Ads API 用戶端程式庫記錄至 Cloud Logging 或其他工具。每個選項都各有利弊,包括實作時間、複雜度和效能。請先仔細思考這些取捨,再決定要實作哪種解決方案。

方法 1:從背景程序將本機記錄寫入雲端

您可以修改記錄設定,將用戶端程式庫記錄寫入電腦上的本機檔案。記錄檔輸出至本機檔案後,您可以設定 Daemon 來收集記錄檔,並將記錄檔傳送至雲端。

這種做法有一個限制,就是預設不會擷取部分成效指標。用戶端程式庫記錄包含要求和回應物件的詳細資料,因此除非進行其他變更以記錄這些資料,否則不會納入延遲指標。

選項 2:在 Compute Engine 上執行應用程式並安裝作業套件代理程式

如果應用程式在 Compute Engine 上執行,您可以安裝作業套件代理程式,將記錄傳送至 Google Cloud Logging。除了預設傳送的指標和記錄還可以設定作業套件代理程式,將應用程式記錄傳送至 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 中的自訂 Google 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 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