天天看點

DRF-Tracking子產品源碼分析

DRF-Tracking子產品源碼分析

一、drf-tracking是什麼?

drf-tracking是為DRF的view通路提供一個日志記錄子產品。使用mixin的方式無縫的和DRF相結合。 從源碼結構上來看也是Django的一個APP項目,提供Model将日志記錄到資料庫、自定Manger操作等.其核心為該源碼中的

base_mixins.py

子產品。個人認為該項目非常适合新手閱讀。

二、基本使用

和其他子產品安裝一樣,使用pip安裝即可。

$ pip install drf-tracking           

之後将項目安裝到項目settings的

INSTALLD_APPS

中,名字是

rest_framework_tracking

,由于需要資料庫,是以還要執行以下migrate操作。

$ python manage.py migrate           

采用面向對象的繼承方式,我們隻需要在每個Class類中繼承該子產品中的

LoggingMixin

方法即可,預設情況下它會記錄所有請求方法的日志。當然我們也可以自定義讓哪些請求方法記錄日志。

logging_methods = ['POST', 'PUT']           

LoggingMixin

類提供了一個類屬性,該屬性的值是一個清單,用于存放要記錄日志的所有HTTP請求方法。

從源碼中我們也不難看出,

LoggingMixin

定義了一個should_log函數來控制日志是否記錄,這個函數也非常簡單, 傳回結果的是一個bool值。如下:

def should_log(self, request, response):
    return self.logging_methods == '__all__' or request.method in self.logging_methods           

如上,我們也可以重寫這個方法根據不同的需求來控制日志是否被記錄。 隻要我們最終傳回一個bool值即可。

而這個函數的調用取決于

finalize_response

這個函數,也是這個函數來封裝記錄資料。

LoggingMixin

中還有一個handle_log方法,這個方法控制如何記錄日志,以及将日志寫到何處。我們來看一下這個函數的代碼。

def handle_log(self):
    APIRequestLog(**self.log).save()           

APIRequestLog

是一個Model表,也就是日志記錄表,是以這段代碼不難了解就是執行個體化一個APIRequestLog執行個體,然後調用save()方法儲存。 而我們在這個類中也隻是封裝一個全局的

log

屬性即可。 可以猜出這個log儲存的也就是每個表結構中以字段為key的字典。

到目前為止,我們知道了日志如何儲存,如何控制日志是否儲存,但好像并不知道這個

log

是如封裝的。

三、請求和響應鈎子

我們知道DRF封裝了Django原生的View提供了一個我們常用的APIView,其中有一個initial的方法。 這個方法主要是做一些請求檢查。 當然,我們可以重寫這個類,并在此時就開始對請求做日志初始化和記錄。

drf-tracking

就是這麼做的,我們來看下這個方法:

def initial(self, request, *args, **kwargs):
    # 初始化我們需要的log字典
    self.log = {}
    #封裝請求體資料
    self.log['requested_at'] = now()
    self.log['data'] = self._clean_data(request.body)

    super(BaseLoggingMixin, self).initial(request, *args, **kwargs)

    try:
        data = self.request.data.dict()
    except AttributeError:
        data = self.request.data
    self.log['data'] = self._clean_data(data)           

我們知道

super()

是執行目前類MRO清單中下一個類中對應的方法,這樣我們使用

super()

函數就可以直接執行

APIView

中的initial方法,這也是一種為一個函數添加功能的技巧。

同理,finalize_response也是一個被重寫的方法,我們來看看這個方法:

def finalize_response(self, request, response, *args, **kwargs):
        #執行父類的方法
        response = super(BaseLoggingMixin, self).finalize_response(request, response, *args, **kwargs)
        
        # 擷取自定義或者預設的should_log方法,來判斷是否記錄日志
        # Ensure backward compatibility for those using _should_log hook
        should_log = self._should_log if hasattr(self, '_should_log') else self.should_log

        if should_log(request, response):
            #實際封裝log部分
            if response.streaming:
                rendered_content = None
            elif hasattr(response, 'rendered_content'):
                rendered_content = response.rendered_content
            else:
                rendered_content = response.getvalue()

            self.log.update(
                {
                    'remote_addr': self._get_ip_address(request),
                    'view': self._get_view_name(request),
                    'view_method': self._get_view_method(request),
                    'path': request.path,
                    'host': request.get_host(),
                    'method': request.method,
                    'query_params': self._clean_data(request.query_params.dict()),
                    'user': self._get_user(request),
                    'response_ms': self._get_response_ms(),
                    'response': self._clean_data(rendered_content),
                    'status_code': response.status_code,
                }
            )
            try:
                # 調用handle_log 來儲存封裝的日志
                if not connection.settings_dict.get('ATOMIC_REQUESTS'):
                    self.handle_log()
                else:
                    if getattr(response, 'exception', None) and connection.in_atomic_block:
                        connection.set_rollback(True)
                        connection.set_rollback(False)
                    self.handle_log()
            except Exception:
                logger.exception('Logging API call raise exception!')

        return response           

到此,我們也就看完了整個drf-tracking的源碼核心部分,重寫一個元件的其他部分來擴充其功能。 當然,如果我們詳讀DRF的源碼可以看到,DRF也是重寫了Django的view提供了強大的請求功能。

四、自定功能

剛在github上看到這個項目的時候粗略的看了下使用,發現其擴充性不是很好。 以至于覺得不是很好用。後來仔細閱讀源碼後(也是從它重寫DRF的元件啟發了我)可擴充性還是很好的。 這裡介紹一下自己擴充的DEMO.

注: 這中情況我們就不需要再在settings中INSTALL_APP添加rest_framework_tracking.

自定義資料庫

drf-tracking自帶的資料庫隻提供了一些簡單的字段,如果需要記錄我們業務上的資料就需要重寫,這裡以一個告警的需求為列,我們來自定義Model。

class CustomApiLog(BaseAPIRequestLog):
    subject = models.TextField(verbose_name='告警主題',default=None,null=True,blank=True)
    sub_text = models.TextField(verbose_name='告警内容', default=None,null=True,blank=True)           

添加告警主題和告警内容兩個字段,當然自己也可以根據需求添加。

自定義mixin

上面講到LoggingMixin 類就提供了handle_log方法,而這個方法知識儲存資料庫資料,我們可以重寫這個方法,并在儲存資料庫之前擷取到我們自定義字段的資料。下來來看看如果需要自定義mixin我需要做些什麼。

1、settings指定你自定義的Model

LOG_MODEL = 'app.CustomApiLog'           

2、來看看我自定的mixin

from rest_framework_tracking.base_mixins import BaseLoggingMixin
from rest_framework_tracking.base_models import BaseAPIRequestLog
from django.conf import settings
from django.apps import apps as django_apps
from django.core.exceptions import ImproperlyConfigured

def get_log_model():
    try:
        return django_apps.get_model(settings.LOG_MODEL, require_ready=False)
    except ValueError:
        raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
    except LookupError:
        raise ImproperlyConfigured(
            "AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL
        )

class CustomLoggingMixin(BaseLoggingMixin):

    def _get_custom_fileds(self):
        # 擷取自定義的log表
        self.CustomApiLog = get_log_model()
        #擷取自定義的表字段
        custom_filed = (item.name for item in set(self.CustomApiLog._meta.fields) - set(BaseAPIRequestLog._meta.fields))
        # 更新log字典
        for item in custom_filed:
            if hasattr(self,'get_%s' % item):
                func = getattr(self, 'get_%s' % item)
                result = func()
                self.log.update({item: result})

    def handle_log(self):

        self._get_custom_fileds()
        self.CustomApiLog(**self.log).save()           

自定義字段需要你自己編寫函數來傳回你想要的資料,這個的函數命名有一定的講究,他必須是以

get_字段名

指令的函數,例如上面的subject字段,其函數名為get_subject.

def get_subject(self):
    return '【{user}】操作接口【{interface}】{operator}一條資料'.format(user=self._get_user(self.request),\
        interface=self.request._request.path,operator=self.method_dict.get(self.request.method.upper()))           

當然,你可以重寫我這個提供專門提供自定義字段的所有函數并處理業務邏輯,這并不會對日志記錄有任何影響。

參考:[

https://github.com/aschn/drf-tracking>]()

繼續閱讀