Logging

Logging dan pemantauan bekerja secara bersamaan untuk membantu Anda memahami dan mengoptimalkan performa aplikasi, serta 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 memberikan log panggilan API saat memerlukan dukungan teknis.

Logging library klien

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

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 menghasilkan log mendetail dan log ringkasan untuk setiap panggilan API. Log mendetail berisi semua detail panggilan API, sedangkan log ringkasan berisi detail minimal panggilan API. Contoh setiap jenis log ditampilkan, dengan log yang 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.")

Log 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 merekam detail panggilan API keluar dan masuk. Anda harus mencatat setidaknya nilai header respons request-id, yang kemudian dapat dibagikan kepada tim dukungan teknis sesuai kebutuhan.

Membuat log ke cloud

Ada banyak alat yang dapat Anda gunakan untuk mengambil log dan metrik performa untuk aplikasi Anda. Misalnya, Anda dapat menggunakan Google Cloud Logging untuk mencatat metrik performa ke Project Google Cloud. Hal ini memungkinkan Anda menyiapkan 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 Perl. Jadi, dalam sebagian besar kasus, Anda dapat mencatat log dengan Cloud Logging langsung dari integrasi library klien. Untuk bahasa lain, termasuk Perl, Cloud Logging juga menawarkan REST API.

Ada beberapa opsi untuk mencatat ke Cloud Logging, atau alat lain, dari library klien Google Ads API. Setiap opsi memiliki konsekuensinya sendiri terkait waktu untuk diimplementasikan, kompleksitas, dan performa. Pikirkan dengan cermat 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 di-output 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 mencatatnya juga.

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

Jika aplikasi Anda berjalan di Compute Engine, Anda dapat mengirim log ke Google Cloud Logging dengan menginstal Ops Agent. Agen Ops 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 tepat untuk dipertimbangkan.

Opsi 3: Terapkan logging di kode aplikasi Anda

Membuat log langsung dari kode aplikasi dapat dilakukan dengan salah satu dari dua cara:

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

  2. Mengimplementasikan antarmuka logging. Jika logika aplikasi dapat di-abstrak 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 pernyataan log di seluruh kode aplikasi, karena lebih mudah dikelola dan diskalakan. Untuk codebase yang lebih besar, kemudahan pemeliharaan dan skalabilitas solusi ini lebih relevan.

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

Opsi 4: Mengimplementasikan interceptor logging gRPC kustom

gRPC mendukung interceptor streaming dan unary yang dapat mengakses objek permintaan dan respons saat diteruskan antara klien dan server. Library klien Google Ads API menggunakan interceptor gRPC untuk menawarkan dukungan logging bawaan. Demikian pula, Anda dapat menerapkan interceptor gRPC kustom untuk mengakses objek permintaan dan respons, mengekstrak informasi untuk tujuan logging dan pemantauan, serta menulis data tersebut ke lokasi pilihan Anda.

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

Interceptor Google Cloud Logging kustom di Python

Untuk mendemonstrasikan solusi ini, kami telah menulis contoh pengendali logging kustom di Python. Interceptor kustom dibuat dan diteruskan ke klien layanan. Kemudian, objek ini mengakses objek permintaan dan respons yang diteruskan pada setiap panggilan metode layanan, memproses data dari objek tersebut, dan mengirim data ke Google Cloud Logging.

Selain data yang berasal dari objek permintaan dan respons, contoh ini menerapkan beberapa logika tambahan untuk merekam waktu yang berlalu dari permintaan, dan beberapa metadata lain yang akan berguna untuk tujuan pemantauan, seperti apakah permintaan berhasil atau tidak. Untuk informasi selengkapnya tentang kegunaan informasi ini, baik secara umum untuk pemantauan, maupun secara khusus 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