記錄

記錄與監控工作相輔相成,協助您瞭解及最佳化 應用程式效能,以及診斷錯誤和系統相關 以負載平衡機制分配流量 即可降低應用程式發生效能問題的風險建議您為所有 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 呼叫的最微細節。 以下顯示每種記錄類型的範例,其中部分記錄會遭到截斷及格式化 以便閱讀

摘要記錄

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 用戶端提供用戶端程式庫。 因此在大部分的情況下,您可以使用 直接從用戶端程式庫整合使用 Cloud Logging。其他語言 包括 Perl,Cloud Logging 也提供 REST API

有幾種方法可將記錄至 Cloud Logging 或其他工具 Google Ads API 用戶端程式庫。每種方案都各有時間取捨 實作、複雜性和效能因此,請務必謹慎考慮 再決定是否導入

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

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

這種做法的缺點之一是 預設擷取。用戶端程式庫記錄檔包含要求的詳細資料 回應物件,因此除非進行額外變更,否則不會納入延遲時間指標 也會將這些資料納入記錄中。

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

如果您的應用程式是在 Compute Engine 上執行,您可以傳送 安裝作業套件代理程式,將記錄檔寫入 Google Cloud Logging。運作 可以設定代理程式,將應用程式記錄檔傳送至 Cloud 記錄,除了預設傳送的指標和記錄之外。

如果您的應用程式已在 Google Cloud 環境中執行, 考慮將應用程式遷移至 Google Cloud 從不同角度與觀點考量問題 確保我們調查與呈現的是需要考慮的重點

做法 3:在應用程式程式碼中實作記錄功能

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

  1. 在每 程式碼中的相關位置這個選項較適合用於 這類變更的範圍和維護成本 極簡

  2. 實作記錄介面。如果應用程式邏輯可以擷取 讓應用程式的不同部分沿用自同一基礎 類別,記錄邏輯可在該基礎類別中實作。這個選項 通常比起將記錄陳述式 程式碼維護和擴充都更加容易。大型適用 這個解決方案具備的可維護性和擴充性

這種方法的一項限制是,完整的要求和回應記錄檔 而無法使用這項資訊完整的要求和回應物件 透過 gRPC 攔截器存取;這是內建用戶端程式庫 Logging 會取得要求與回應記錄。如果發生錯誤,請提供其他 這個例外狀況物件可提供的資訊,但詳細資料會比較少 在應用程式邏輯內成功回應。例如,在 大多數的情況下, 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