Logging

Pencatatan log dan pemantauan bekerja bersama-sama untuk membantu Anda memahami dan mengoptimalkan performa aplikasi, serta untuk mendiagnosis error dan masalah yang masalah performa. Anda harus mengaktifkan ringkasan log untuk semua panggilan API dan log mendetail untuk panggilan API yang gagal sehingga Anda dapat menyediakan API log panggilan saat memerlukan dukungan teknis.

Logging library klien

Library klien Google Ads API dilengkapi dengan logging bawaan. Untuk platform tertentu detail {i>logging<i}, lihat dokumentasi {i>logging<i} dalam {i>library <i}klien Anda pilihan.

Language Panduan
Java Dokumen logging untuk Java
.NET Dokumen logging untuk .NET
PHP Dokumen logging untuk PHP
Python Dokumen logging untuk Python
Ruby Dokumen logging untuk Ruby
Perl Dokumen logging untuk Perl

Format log

Library klien Google Ads API membuat log detail dan ringkasan log untuk setiap panggilan API. Log terperinci berisi semua detail dari panggilan API, sedangkan log ringkasan berisi detail minimal panggilan API. Contoh setiap jenis log ditampilkan, dengan log terpotong dan diformat untuk keterbacaan.

Log ringkasan

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.")

Catatan mendetail

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----------------

Bagaimana jika saya tidak menggunakan library klien?

Jika Anda tidak menggunakan pustaka klien, terapkan {i>logging<i} Anda sendiri untuk merekam detail panggilan API keluar dan masuk. Anda harus mencatat setidaknya nilai header respons request-id, yang kemudian dapat dibagikan dengan tim dukungan teknis sesuai kebutuhan.

Logging ke cloud

Ada banyak alat yang dapat Anda gunakan untuk mengambil log dan metrik kinerja untuk aplikasi Anda. Misalnya, Anda dapat menggunakan Google Cloud Logging untuk mencatat metrik performa ke Project Google Cloud Anda. Hal ini membuatnya penyiapan dasbor dan pemberitahuan di Google Cloud Monitoring untuk memanfaatkan metrik yang dicatat.

Cloud Logging menawarkan library klien untuk semua klien Google Ads API yang didukung bahasa library kecuali untuk Perl, sehingga dalam banyak kasus kita bisa mencatat dengan Cloud Logging langsung dari integrasi library klien Anda. Untuk bahasa lain termasuk Perl, Cloud Logging juga menawarkan REST API.

Ada beberapa opsi untuk logging ke Cloud Logging, atau alat lainnya, dari Library klien Google Ads API. Setiap opsi memiliki kompromi waktu masing-masing untuk implementasi, kompleksitas, dan performa. Pikirkan baik-baik kompromi ini sebelum memutuskan solusi mana yang akan diterapkan.

Opsi 1: Menulis log lokal ke cloud dari proses latar belakang

Log pustaka klien dapat ditulis ke file lokal pada komputer Anda dengan memodifikasi konfigurasi logging Anda. Setelah log merupakan {i>output<i} ke file lokal, Anda dapat menyiapkan {i>daemon<i} untuk mengumpulkan log dan mengirimkannya ke {i>cloud<i}.

Salah satu batasan pendekatan ini adalah bahwa beberapa metrik kinerja tidak akan diambil secara default. Log pustaka klien menyertakan detail dari permintaan dan objek respons, sehingga metrik latensi tidak akan disertakan kecuali jika ada perubahan tambahan dibuat untuk mencatatnya juga.

Opsi 2: Jalankan aplikasi Anda di Compute Engine dan instal Agen Operasional

Jika aplikasi Anda berjalan di Compute Engine, Anda dapat mengirim log ke Google Cloud Logging dengan menginstal Agen Operasional. Operasi Agen dapat dikonfigurasi untuk mengirim log aplikasi Anda ke Cloud Logging, selain metrik dan log yang dikirim secara default.

Jika aplikasi Anda sudah berjalan di lingkungan Google Cloud, atau jika Anda mempertimbangkan untuk memindahkan aplikasi Anda ke Google Cloud, ini adalah opsi yang bagus yang perlu dipertimbangkan.

Opsi 3: Terapkan logging dalam kode aplikasi Anda

Logging langsung dari kode aplikasi dapat dilakukan dengan salah satu dari dua cara berikut:

  1. Menggabungkan kalkulasi metrik dan laporan log dalam setiap lokasi yang berlaku dalam kode Anda. Opsi ini lebih cocok untuk proyek yang {i>codebase<i}, di mana ruang lingkup dan biaya pemeliharaan dari perubahan tersebut akan menjadi akan sangat minim.

  2. Mengimplementasikan antarmuka logging. Apakah logika aplikasi dapat diabstraksi sehingga bagian aplikasi yang berbeda mewarisi dari basis yang sama , logika logging dapat diimplementasikan di class dasar tersebut. Opsi ini umumnya lebih disukai daripada menggabungkan laporan log di seluruh kode aplikasi Anda, karena lebih mudah dikelola dan diskalakan. Untuk tugas yang lebih besar codebase, pemeliharaan dan skalabilitas solusi ini merupakan lebih relevan.

Salah satu batasan pendekatan ini adalah log permintaan dan respons yang lengkap tidak tersedia dari kode aplikasi. Objek permintaan dan respons lengkap dapat diakses dari intersepsi gRPC; inilah cara library klien bawaan log akan memperoleh log permintaan dan respons. Jika terjadi error, informasi tambahan informasi mungkin tersedia dalam objek pengecualian, tetapi detail yang lebih sedikit yang tersedia untuk respons yang berhasil dalam logika aplikasi. Misalnya, di kebanyakan kasus, ID permintaan untuk permintaan yang berhasil tidak dapat diakses dari Objek respons Google Ads API.

Opsi 4: Mengimplementasikan interseptor logging gRPC kustom

gRPC mendukung interseptor unary dan streaming yang dapat mengakses objek permintaan dan respons saat mereka melalui klien dan server. Tujuan Library klien Google Ads API menggunakan interseptor gRPC untuk menawarkan logging bawaan dukungan teknis IT. Demikian pula, Anda dapat menerapkan interseptor gRPC kustom untuk mengakses objek permintaan dan respons, mengekstrak informasi untuk logging dan pemantauan tujuan, dan menulis data itu ke lokasi pilihan Anda.

Tidak seperti beberapa solusi lain yang disajikan di sini, menerapkan gRPC kustom intersepsi memberi Anda fleksibilitas untuk menangkap objek permintaan dan respons setiap permintaan, dan menerapkan logika tambahan untuk mencatat detail permintaan. Misalnya, Anda dapat menghitung waktu berlalu untuk permintaan dengan menerapkan logika pengaturan waktu performa dalam pencegat kustom itu sendiri, lalu catat ke Google Cloud Logging agar tersedia untuk pemantauan latensi dalam Google Cloud Monitoring.

Pencegat Google Cloud Logging kustom di Python

Untuk mendemonstrasikan solusi ini, kami telah menulis contoh logging kustom interseptor di Python. Pencegat kustom dibuat dan diteruskan ke klien, atau klien layanan. Selanjutnya, alat ini akan mengakses objek permintaan dan respons yang meneruskan di setiap panggilan metode layanan, memproses data dari objek tersebut, dan mengirimkan data ke Google Cloud Logging.

Selain data yang berasal dari objek permintaan dan respons, mengimplementasikan beberapa logika tambahan untuk merekam waktu berlalu permintaan, dan beberapa {i>metadata<i} lain yang akan berguna untuk tujuan pemantauan, seperti apakah permintaan berhasil atau tidak. Untuk informasi selengkapnya tentang cara dapat berguna, baik secara umum untuk pemantauan, dan khususnya ketika menggabungkan Google Cloud Logging dan Google Cloud Monitoring, lihat Monitoring panduan kami.

# 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