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 หรือเครื่องมืออื่นจากไลบรารีของไคลเอ็นต์ Google Ads API ทำได้หลายวิธี แต่ละตัวเลือกมาพร้อมกับเวลาแลกกับเวลาในการนำไปใช้ ความซับซ้อน และประสิทธิภาพของตัวเอง โปรดคิดให้รอบคอบเกี่ยวกับข้อดีข้อเสียเหล่านี้ ก่อนตัดสินใจว่าจะใช้โซลูชันใด

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

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

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

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

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

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

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

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

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

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

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

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

gRPC รองรับตัวตรวจจับแบบอิสระและสตรีมมิง ซึ่งเข้าถึงออบเจ็กต์คำขอและการตอบกลับได้ขณะที่มีการส่งผ่านระหว่างไคลเอ็นต์และเซิร์ฟเวอร์ ไลบรารีของไคลเอ็นต์ 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