記錄

記錄和監控工作可搭配使用,協助您瞭解及最佳化應用程式效能,以及診斷錯誤和系統相關問題。您應針對所有 API 呼叫啟用摘要記錄,並針對失敗的 API 呼叫啟用詳細記錄,以便在需要技術支援時提供 API 通話記錄。

用戶端程式庫記錄

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

語言 指南
Java Java 的記錄文件
.NET .NET 的 Logging 說明文件
PHP PHP 適用的 Logging 文件
Python Python 適用的 Logging 說明文件
小茹 Ruby 適用的記錄文件
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.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 攔截器存取;這就是內建用戶端程式庫記錄功能取得要求和回應記錄的方式。發生錯誤時,例外狀況物件中可能會提供額外資訊,但應用程式邏輯中成功回應可提供的詳細資料較少。舉例來說,在大多數的情況下,成功請求的要求 ID 無法透過 Google Ads API 回應物件存取。

選項 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 的情況下),請參閱 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