Logging

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

Logging library klien

Library klien Google Ads API dilengkapi dengan logging bawaan. Untuk mengetahui detail logging khusus platform, baca dokumentasi logging dalam library klien pilihan Anda.

Language Panduan
Java Dokumen logging untuk Java
.NET Dokumentasi 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 menghasilkan log mendetail dan log ringkasan untuk setiap panggilan API. Log mendetail berisi semua detail panggilan API, sedangkan log ringkasan berisi sedikit detail panggilan API. Contoh setiap jenis log ditampilkan, dengan log terpotong dan diformat agar mudah dibaca.

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 library klien, terapkan logging Anda sendiri untuk mencatat detail panggilan API keluar dan masuk. Anda harus mencatat setidaknya nilai header respons request-id ke dalam log, yang kemudian dapat dibagikan kepada tim dukungan teknis sesuai kebutuhan.

Logging ke cloud

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

Cloud Logging menawarkan library klien untuk semua bahasa library klien Google Ads API yang didukung kecuali untuk Perl, sehingga dalam sebagian besar kasus, Anda dapat melakukan logging dengan Cloud Logging langsung dari integrasi library klien Anda. Untuk bahasa lain termasuk Perl, Cloud Logging juga menawarkan REST API.

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

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

Log library klien dapat ditulis ke file lokal di komputer Anda dengan mengubah konfigurasi logging. Setelah log dikeluarkan ke file lokal, Anda dapat menyiapkan daemon untuk mengumpulkan log dan mengirimkannya ke cloud.

Salah satu batasan pendekatan ini adalah beberapa metrik performa tidak akan diambil secara default. Log library klien menyertakan detail dari objek permintaan dan respons, sehingga metrik latensi tidak akan disertakan kecuali jika perubahan tambahan dilakukan untuk mencatat log ini 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. Agen Operasional 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 ke Google Cloud, ini adalah opsi yang bagus untuk dipertimbangkan.

Opsi 3: Implementasikan logging dalam kode aplikasi Anda

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

  1. Menggabungkan penghitungan metrik dan laporan log di setiap lokasi yang berlaku dalam kode Anda. Opsi ini lebih cocok untuk codebase yang lebih kecil, dengan biaya cakupan dan pemeliharaan perubahan tersebut akan minimal.

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

Salah satu batasan pendekatan ini adalah log permintaan dan respons yang lengkap tidak tersedia dari kode aplikasi. Objek permintaan dan respons penuh dapat diakses dari interseptor gRPC; ini adalah cara logging library klien bawaan mendapatkan log permintaan dan respons. Jika terjadi error, informasi tambahan mungkin tersedia dalam objek pengecualian, tetapi lebih sedikit detail yang tersedia untuk respons yang berhasil dalam logika aplikasi. Misalnya, pada umumnya, 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 melintasi antara klien dan server. Library klien Google Ads API menggunakan interseptor gRPC untuk menawarkan dukungan logging bawaan. Demikian pula, Anda dapat menerapkan intersepsi gRPC kustom untuk mengakses objek permintaan dan respons, mengekstrak informasi untuk logging dan tujuan pemantauan, serta menulis data tersebut ke lokasi pilihan Anda.

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

Intersepsi Google Cloud Logging kustom di Python

Untuk mendemonstrasikan solusi ini, kami telah menulis contoh interseptor logging kustom di Python. Interseptor kustom dibuat dan diteruskan ke klien layanan. Kemudian, objek permintaan dan respons yang diteruskan pada 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, contoh tersebut mengimplementasikan beberapa logika tambahan untuk mencatat waktu permintaan yang telah berlalu, dan beberapa metadata lain yang akan berguna untuk tujuan pemantauan, seperti apakah permintaan berhasil atau tidak. Untuk informasi selengkapnya tentang manfaat informasi ini, baik secara umum untuk pemantauan, maupun khususnya saat menggabungkan Google Cloud Logging dan Google Cloud Monitoring, lihat Panduan pemantauan.

# 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