جارٍ التسجيل

يعمل التسجيل والمراقبة جنبًا إلى جنب لمساعدتك في فهم أداء التطبيق وتحسينه، فضلاً عن تشخيص الأخطاء والمشاكل المتعلقة بالنظام. بعد قراءة هذا الدليل، اطّلع على دليل المراقبة لمعرفة بعض أفضل الممارسات لمراقبة تطبيقك، بما في ذلك المقاييس المحددة لتسجيل الدخول لأغراض المراقبة.

تسجيل مكتبة العميل

تأتي مكتبات عملاء Google Ads API مزوّدةً بتسجيل مدمج. للحصول على تفاصيل التسجيل الخاصة بنظام التشغيل، راجع وثائق التسجيل داخل مكتبة العميل التي تختارها.

تسجيل الدخول إلى السحابة الإلكترونية

هناك العديد من الأدوات التي يمكنك استخدامها لجمع السجلات ومقاييس الأداء لتطبيقك. على سبيل المثال، يمكنك استخدام ميزة تسجيل الدخول إلى Google Cloud لتسجيل مقاييس الأداء في مشروعك على Google Cloud. ويجعل ذلك من الممكن إعداد لوحات البيانات والتنبيه في Google Cloud Monitor للاستفادة من المقاييس المسجّلة.

يوفّر Cloud Logging مكتبات للعملاء لجميع لغات مكتبة برامج Google Ads API المتوافقة باستثناء لغة Perl، لذلك من الممكن في معظم الحالات التسجيل باستخدام Cloud Logging مباشرةً من عملية دمج مكتبة البرامج. بالنسبة إلى اللغات الأخرى، بما في ذلك Perl، يوفّر Cloud Logging أيضًا REST API.

هناك بضعة خيارات لتسجيل الدخول إلى Cloud Logging، أو أداة أخرى، من مكتبة برامج Google Ads API. يأتي كل خيار بمفاضلاته الخاصة من وقت التنفيذ والتعقيد والأداء. فكر مليًا في هذه المفاضلات قبل تحديد الحل المطلوب تنفيذه.

الخيار 1: كتابة سجلّات محلية على السحابة الإلكترونية من عملية في الخلفية

يمكن كتابة سجلات مكتبة البرامج في ملف محلي على جهازك من خلال تعديل إعدادات التسجيل. بعد إخراج السجلات إلى ملف محلي، يمكنك إعداد برنامج خفي لجمع السجلات وإرسالها إلى السحابة.

يتمثل أحد قيود هذا المنهج في أنّ بعض مقاييس الأداء لن يتم تسجيلها بشكلٍ تلقائي. تتضمّن سجلات مكتبة البرامج تفاصيل من كائنات الطلب والاستجابة، وبالتالي لن يتم تضمين مقاييس وقت الاستجابة ما لم يتم إجراء تغييرات إضافية لتسجيل هذه البيانات أيضًا.

الخيار 2: تشغيل تطبيقك على Compute Engine وتثبيت وكيل العمليات

إذا كان تطبيقك قيد التشغيل على Compute Engine، يمكنك إرسال سجلاتك إلى Google Cloud Logging من خلال تثبيت وكيل العمليات. يمكن ضبط وكيل العمليات لإرسال سجلات التطبيق إلى Cloud Logging، بالإضافة إلى المقاييس والسجلات التي يتم إرسالها تلقائيًا.

إذا كان تطبيقك يعمل حاليًا في بيئة Google Cloud Platform (GCP) أو إذا كنت تريد نقل تطبيقك إلى Google Cloud Platform، سيكون هذا خيارًا رائعًا يجب مراعاته.

الخيار 3: تنفيذ تسجيل الدخول إلى رمز التطبيق

يمكن تسجيل الدخول مباشرةً من رمز التطبيق بإحدى الطريقتين التاليتين:

  1. دمج العمليات الحسابية للمقاييس وبيانات السجلّ في كل موضع سارٍ في الرمز. هذا الخيار أكثر ملاءمة لقواعد التعليمات البرمجية الأصغر، حيث تكون تكاليف النطاق والصيانة لهذا التغيير ضئيلة.

  2. تنفيذ واجهة تسجيل. إذا كان من الممكن استخراج منطق التطبيق بحيث تكتسب أجزاء مختلفة من التطبيق من الفئة الأساسية نفسها، يمكن تنفيذ منطق تسجيل الدخول في هذه الفئة الأساسية. ويفضل هذا الخيار بشكل عام على دمج عبارات السجل في جميع أنحاء رمز التطبيق، حيث إنه يسهل صيانته وتوسيع نطاقه. بالنسبة إلى قواعد الرموز الأكبر حجمًا، تكون قابلية صيانة هذا الحل وقابلية توسّعه أكثر صلة بالموضوع.

يتمثل أحد قيود هذا المنهج في أن سجلات الطلبات والاستجابة الكاملة غير متاحة من رمز التطبيق. ويمكن الوصول إلى كائنات الطلب والاستجابة الكاملة من خلال اعتراضات gRPC، وهذه هي الطريقة التي تحصل من خلالها تسجيل مكتبة البرامج المضمنة على سجلات الطلبات والاستجابة. في حالة حدوث خطأ، قد تتوفر معلومات إضافية في كائن الاستثناء، لكن يتوفر أقل من التفاصيل للاستجابات الناجحة ضمن منطق التطبيق. على سبيل المثال، في معظم الحالات، لا يمكن الوصول إلى رقم تعريف الطلب الناجح من كائنات استجابة Google Ads API.

الخيار 4: تنفيذ اعتراض مخصّص لتسجيل gRPC

تدعم gRPC أدوات الاعتراض التي يمكنها الوصول إلى كائنات الطلب والاستجابة أثناء انتقالها بين العميل والخادم. تستخدم مكتبات برامج Google Ads API أدوات اعتراض gRPC لتوفير دعم مضمَّن للتسجيل. وبالمثل، يمكنك تنفيذ أداة اعتراض gRPC مخصّصة للوصول إلى كائنات الطلب والاستجابة، واستخراج المعلومات لتسجيل الأغراض ومراقبتها، وكتابة تلك البيانات في الموقع الذي تختاره.

على عكس بعض الحلول الأخرى المقدَّمة هنا، يمنحك تنفيذ أداة اعتراض مخصّصة gRPC مرونة في التقاط كائنات الطلب والاستجابة في كل طلب، وتنفيذ إجراءات منطقية إضافية لتسجيل تفاصيل الطلب. على سبيل المثال، يمكنك حساب الوقت المنقضي من أحد الطلبات من خلال تنفيذ منطق توقيت الأداء ضمن أداة الاعتراض المخصّصة نفسها، ثم تسجيل المقياس في Google Cloud Logging بهدف إتاحته لمراقبة وقت الاستجابة ضمن Google Cloud Monitoring.

اعتراض تسجيل الدخول إلى Google Cloud المخصّص في بايثون

لتوضيح هذا الحل، كتبنا مثالاً لاعتراض التسجيل المخصص في بايثون. يتم إنشاء الاعتراض المخصّص وتمريره إلى برنامج الخدمة. تصل بعد ذلك إلى كائنات الطلب والاستجابة التي تمر في كل استدعاء لطريقة الخدمة، وتعالج البيانات من هذه الكائنات، وترسل البيانات إلى 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