記錄

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

用戶端程式庫記錄功能

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

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

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

選項 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