如何在 Django 中记录请求和响应?

Mas*_*Man 9 django logging http gunicorn django-rest-framework

如何使用中间件在 Django 中记录我的所有请求和响应(标头和正文)?我将 Django 2.2 与 Django rest 框架一起使用,所以有时请求和响应是原始 Django 类型,有时是 drf。该应用程序在 gunicorn 后面提供。我开发了中间件,但主要问题是我无法读取请求的正文两次,因为它给了我错误。

Fel*_*löf 10

这是在数据库中记录请求的示例。

注意:这将在每个请求中额外访问一次数据库。所以它会减慢响应时间。

模型.py

class Request(models.Model):
    endpoint = models.CharField(max_length=100, null=True) # The url the user requested
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) # User that made request, if authenticated
    response_code = models.PositiveSmallIntegerField() # Response status code
    method = models.CharField(max_length=10, null=True)  # Request method
    remote_address = models.CharField(max_length=20, null=True) # IP address of user
    exec_time = models.IntegerField(null=True) # Time taken to create the response
    date = models.DateTimeField(auto_now=True) # Date and time of request
    body_response = models.TextField() # Response data
    body_request = models.TextField() # Request data
Run Code Online (Sandbox Code Playgroud)

中间件.py

class SaveRequest:
    def __init__(self, get_response):
        self.get_response = get_response

        # Filter to log all request to url's that start with any of the strings below.
        # With example below:
        # /example/test/ will be logged.
        # /other/ will not be logged.
        self.prefixs = [
            '/example'
        ]

    def __call__(self, request):
        _t = time.time() # Calculated execution time.
        response = self.get_response(request) # Get response from view function.
        _t = int((time.time() - _t)*1000)    

        # If the url does not start with on of the prefixes above, then return response and dont save log.
        # (Remove these two lines below to log everything)
        if not list(filter(request.get_full_path().startswith, self.prefixs)): 
            return response 

        # Create instance of our model and assign values
        request_log = Request(
            endpoint=request.get_full_path(),
            response_code=response.status_code,
            method=request.method,
            remote_address=self.get_client_ip(request),
            exec_time=_t,
            body_response=str(response.content),
            body_request=str(request.body)
        )

        # Assign user to log if it's not an anonymous user
        if not request.user.is_anonymous:
            request_log.user = request.user

        # Save log in db
        request_log.save() 
        return response

    # get clients ip address
    def get_client_ip(self, request):
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            _ip = x_forwarded_for.split(',')[0]
        else:
            _ip = request.META.get('REMOTE_ADDR')
        return _ip
Run Code Online (Sandbox Code Playgroud)

设置.py

# Activate the middleware in settings.py like this.
MIDDLEWARE = [
    ... # Django default middleware
    '<your_appname>.middleware.SaveRequest'
]
Run Code Online (Sandbox Code Playgroud)

  • 你显然没有阅读或理解这个问题。 (2认同)

Mas*_*Man 1

我最初尝试做类似的事情,request = copy.copy(request)但显然这是一个错误,因为浅复制不会复制嵌套对象。所以正确的做法是(__call__是中间件的类实例方法):

\n
def __call__(self, request):\n    request_body = copy.copy(request.body)\n    # Here goes more code for further processing\n    # and now I can safely use request_body before or after\n    # the view code runs\n
Run Code Online (Sandbox Code Playgroud)\n

更新

\n

正如Felix Ekl\xc3\xb6f所建议的,您还可以使用str(request.body)python 来处理复制正文内容,因为字符串在 python 中是不可变的。(我想它也具有更好的可读性)。

\n