日志记录

Logging 和 Monitoring 协同工作,帮助您了解和优化 并诊断错误和与系统相关的 问题。您应为所有 API 调用开启摘要日志,并 有关失败的 API 调用的详细日志,以便您提供 API 在需要技术支持时查看通话记录。

客户端库日志记录

Google Ads API 客户端库自带内置日志记录功能。对于针对具体平台的 请参阅您的客户端库中的日志记录文档, 选择。

语言 指南
Java 适用于 Java 的 Logging 文档
.NET 适用于 .NET 的 Logging 文档
PHP 适用于 PHP 的 Logging 文档
Python 适用于 Python 的 Logging 文档
Ruby 适用于 Ruby 的 Logging 文档
Perl 适用于 Perl 的日志记录文档

日志格式

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:从后台进程将本地日志写入云端

您可以将客户端库日志写入到计算机上的本地文件中,方法是修改 日志记录配置将日志输出到本地文件后,您可以 设置一个守护程序来收集日志并将其发送到云端。

这种方法的局限性在于 默认捕获信息客户端库日志包含相关请求的详细信息和 响应对象,因此除非发生其他更改,否则延迟时间指标不会包含在内 来记录这些内容

方法 2:在 Compute Engine 上运行应用并安装 Ops Agent

如果您的应用在 Compute Engine 上运行,则可以将您的 安装 Ops Agent,将日志保存到 Google Cloud Logging。Ops Agent 支持您 可以配置代理,以便将应用日志发送到 Cloud Logging,以及默认发送的指标和日志

如果您的应用已在 Google Cloud 环境中运行,或者您 正在考虑将您的应用迁移到 Google Cloud,这是一个不错的选择, 。

方法 3:在应用代码中实现日志记录

可通过以下两种方式之一,直接从应用代码记录日志:

  1. 将指标计算和日志语句整合到 代码中的相应位置对于 代码库,此类更改的范围和维护成本为 极简。

  2. 实现日志记录接口。如果应用逻辑可以抽象化处理 以便应用的不同部分从同一个基础组件继承 类,那么日志记录逻辑就可以在该基类中实现。此选项 通常比将日志语句整合到 应用代码,因为它更易于维护和扩展。较大 此解决方案的可维护性和可伸缩性 更具相关性。

此方法的一个限制是,完整的请求和响应日志 无法应用代码实现完整的请求和响应对象可以 可从 gRPC 拦截器访问;这就是内置客户端库 logging 可获取请求和响应日志。如果发生错误 但异常对象中可能会提供少量信息, 适用于应用逻辑内的成功响应。例如,在 在大多数情况下,成功请求的请求 ID 无法从 Google Ads API 响应对象。

选项 4:实现自定义 gRPC 日志记录拦截器

gRPC 支持一元和流式拦截器,这些拦截器可以访问 请求和响应对象时,这些对象在客户端和服务器之间传递。通过 Google Ads API 客户端库使用 gRPC 拦截器提供内置日志记录功能 联系。同样,您可以实现自定义 gRPC 拦截器来访问 请求和响应对象,提取用于日志记录和监控的信息 并将这些数据写入您选择的位置

与此处介绍的一些其他解决方案不同, 通过拦截器,您可以灵活地捕获请求和响应对象, 并实现其他逻辑以捕获请求的详细信息。 例如,要计算请求的耗时,您可以 自定义拦截器本身的性能计时逻辑,然后记录 指标上传至 Google Cloud Logging,使其可用于延迟监控 Google Cloud Monitoring 中。

在 Python 中自定义 Google Cloud Logging 拦截器

为了演示此解决方案,我们编写了一个自定义日志记录示例 拦截器。创建自定义拦截器并将其传递到 服务客户端。然后访问请求和响应对象, 处理来自这些对象的数据,以及 将数据发送到 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