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 ซึ่งช่วยให้ตั้งค่าหน้าแดชบอร์ดและการตั้งการแจ้งเตือนใน Google Cloud Monitoring ได้เพื่อใช้ประโยชน์จากเมตริกที่บันทึกไว้

การบันทึกในระบบคลาวด์มีไลบรารีของไคลเอ็นต์สำหรับภาษาของไลบรารีของไคลเอ็นต์ Google Ads API ที่รองรับทั้งหมด ยกเว้น Perl ดังนั้นในกรณีส่วนใหญ่ คุณจะบันทึกด้วยระบบการบันทึกในระบบคลาวด์ได้โดยตรงจากการผสานรวมไลบรารีของไคลเอ็นต์ สําหรับภาษาอื่นๆ รวมถึง Perl การบันทึกในระบบคลาวด์ยังมี REST API ด้วย

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Interceptor ของ 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