Logging

การบันทึกและการตรวจสอบจะทำงานควบคู่กันไปเพื่อช่วยให้คุณเข้าใจและเพิ่มประสิทธิภาพของแอปพลิเคชัน รวมถึงวินิจฉัยข้อผิดพลาดและปัญหาเกี่ยวกับระบบ คุณควรเปิดบันทึกสรุปสำหรับการเรียก API ทั้งหมดและบันทึกโดยละเอียดสำหรับการเรียก API ที่ล้มเหลว เพื่อให้คุณสามารถจัดทำบันทึกการเรียก API เมื่อจำเป็นต้องรับการสนับสนุนด้านเทคนิค

การบันทึกไลบรารีของไคลเอ็นต์

ไลบรารีของไคลเอ็นต์ Google Ads API มาพร้อมกับการบันทึกในตัว สำหรับรายละเอียดการบันทึกเฉพาะแพลตฟอร์ม โปรดดูเอกสารการบันทึกภายในไลบรารีของไคลเอ็นต์ที่คุณต้องการ

ภาษา คู่มือ
Java เอกสารการบันทึกสำหรับ Java
.NET เอกสารการบันทึกสำหรับ .NET
PHP เอกสารการบันทึกสำหรับ PHP
Python เอกสารการบันทึกสำหรับ Python
Ruby เอกสารบันทึกสำหรับ 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 อีกด้วย

การบันทึกไปยัง Cloud Logging หรือเครื่องมืออื่นมี 2-3 ตัวเลือกจากไลบรารีของไคลเอ็นต์ Google Ads API แต่ละตัวเลือกมีข้อดีข้อเสียเวลาในการดำเนินการ ความซับซ้อน และประสิทธิภาพต่างกันไป โปรดคิดให้รอบคอบเกี่ยวกับข้อดีข้อเสียเหล่านี้ ก่อนตัดสินใจว่าจะใช้โซลูชันใด

ตัวเลือกที่ 1: เขียนบันทึกในเครื่องไปยังระบบคลาวด์จากกระบวนการเบื้องหลัง

เขียนบันทึกไลบรารีของไคลเอ็นต์ไปยังไฟล์ในเครื่องโดยแก้ไขการกำหนดค่าการบันทึกได้ เมื่อส่งออกบันทึกไปยังไฟล์ในเครื่องแล้ว คุณสามารถตั้งค่า Daemon เพื่อรวบรวมบันทึกและส่งไปยังระบบคลาวด์ได้

ข้อจำกัดอย่างหนึ่งของวิธีนี้คือระบบจะไม่บันทึกเมตริกประสิทธิภาพบางรายการโดยค่าเริ่มต้น บันทึกไลบรารีของไคลเอ็นต์จะมีรายละเอียดจากออบเจ็กต์คำขอและการตอบกลับ ระบบจะไม่รวมเมตริกเวลาในการตอบสนอง เว้นแต่จะมีการเปลี่ยนแปลงเพิ่มเติมเพื่อบันทึกสิ่งเหล่านี้ด้วย

ตัวเลือกที่ 2: เรียกใช้แอปพลิเคชันบน Compute Engine และติดตั้ง Ops Agent

หากแอปพลิเคชันของคุณทำงานอยู่ใน Compute Engine คุณจะส่งบันทึกไปยัง Google Cloud Logging โดยการติดตั้ง Ops Agent Ops Agent กำหนดค่าได้ให้ส่งบันทึกแอปพลิเคชันไปยัง Cloud L Logging ได้ นอกเหนือจากเมตริกและบันทึกที่ส่งโดยค่าเริ่มต้น

หากแอปพลิเคชันของคุณทำงานอยู่ในสภาพแวดล้อม Google Cloud อยู่แล้ว หรือหากคุณกำลังพิจารณาย้ายแอปพลิเคชันไปยัง Google Cloud เราขอแนะนำให้คุณพิจารณาใช้

ตัวเลือกที่ 3: ใช้การบันทึกในโค้ดของแอปพลิเคชัน

การบันทึกโดยตรงจากโค้ดของแอปพลิเคชันสามารถทำได้โดยใช้ 1 ใน 2 วิธีต่อไปนี้

  1. รวมการคำนวณเมตริกและคำสั่งบันทึกในทุกตำแหน่งที่เกี่ยวข้องในโค้ดของคุณ ตัวเลือกนี้มีประโยชน์มากกว่าสำหรับโค้ดเบสขนาดเล็ก ซึ่งขอบเขตและต้นทุนการบำรุงรักษาของการเปลี่ยนแปลงดังกล่าวน้อยมาก

  2. การใช้อินเทอร์เฟซการบันทึก หากตรรกะของแอปพลิเคชันติดเป็นแอบสแตรกต์ได้เพื่อให้ส่วนต่างๆ ของแอปพลิเคชันรับช่วงมาจากคลาสฐานเดียวกัน คุณจะใช้ตรรกะการบันทึกได้ในคลาสฐานนั้นได้ โดยทั่วไปแล้ว ตัวเลือกนี้เป็นตัวเลือกที่ดีกว่าการรวมคำสั่งบันทึกตลอดทั้งโค้ดแอปพลิเคชัน เนื่องจากทำให้จัดการและปรับขนาดได้ง่ายกว่า สำหรับโค้ดเบสขนาดใหญ่ ความสามารถในการบำรุงรักษาและความสามารถในการปรับขนาดของโซลูชันนี้ล้วนมีความเกี่ยวข้องมากกว่า

ข้อจำกัดอย่างหนึ่งของวิธีนี้คือคำขอและบันทึกการตอบกลับทั้งหมดจะไม่มีอยู่ในโค้ดของแอปพลิเคชัน ออบเจ็กต์คำขอและการตอบกลับทั้งหมดเข้าถึงได้จากตัวดักจับ gRPC ซึ่งเป็นวิธีที่การบันทึกไลบรารีไคลเอ็นต์ในตัวจะรับบันทึกคำขอและการตอบกลับ ในกรณีที่เกิดข้อผิดพลาด อาจมีข้อมูลเพิ่มเติมในออบเจ็กต์ข้อยกเว้น แต่จะมีรายละเอียดน้อยกว่าสำหรับการตอบกลับที่สำเร็จภายในตรรกะของแอปพลิเคชัน ตัวอย่างเช่น ในกรณีส่วนใหญ่ ออบเจ็กต์การตอบกลับของ Google Ads API จะเข้าถึงรหัสคำขอที่สำเร็จไม่ได้

ตัวเลือกที่ 4: ใช้ตัวดักจับการบันทึก gRPC ที่กำหนดเอง

gRPC รองรับ Interceptor แบบสตรีมมิงและแบบไม่พร้อมกัน ซึ่งเข้าถึงออบเจ็กต์คำขอและออบเจ็กต์การตอบกลับได้เมื่อส่งผ่านระหว่างไคลเอ็นต์และเซิร์ฟเวอร์ ไลบรารีของไคลเอ็นต์ Google Ads API ใช้ตัวสกัดกั้น gRPC เพื่อรองรับการบันทึกในตัว ในทำนองเดียวกัน คุณอาจใช้ตัวสกัดกั้น gRPC ที่กำหนดเองเพื่อเข้าถึงออบเจ็กต์คำขอและการตอบกลับ ดึงข้อมูลเพื่อวัตถุประสงค์ในการบันทึกและตรวจสอบ และเขียนข้อมูลนั้นไปยังตำแหน่งที่คุณต้องการ

การใช้ตัวตัด gRPC ที่กำหนดเองช่วยให้คุณบันทึกออบเจ็กต์คำขอและการตอบกลับในทุกคำขอได้อย่างยืดหยุ่น รวมถึงปรับใช้ตรรกะเพิ่มเติมเพื่อบันทึกรายละเอียดของคำขอ ซึ่งต่างจากโซลูชันอื่นๆ ที่นำเสนอที่นี่ เช่น คุณสามารถคำนวณเวลาที่ผ่านไปของคำขอได้โดยใช้ตรรกะเวลาของประสิทธิภาพภายในตัวดักจับที่กำหนดเอง จากนั้นบันทึกเมตริกลงใน Google Cloud Logging เพื่อให้สามารถใช้การตรวจสอบเวลาในการตอบสนองภายใน Google Cloud Monitoring ได้

ตัวดักจับ Google Cloud Logging ที่กำหนดเองใน Python

เพื่อสาธิตการใช้โซลูชันนี้ เราได้เขียนตัวอย่างของตัวตัดบันทึกแบบกำหนดเองใน 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