DRF

Wesley13
• 阅读 611

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
2年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
2年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这