【问题标题】:Logging requests to django-rest-framework记录对 django-rest-framework 的请求
【发布时间】:2013-03-12 19:37:12
【问题描述】:

出于调试目的,我想使用 Django 的日志记录机制来记录每个“到达”django-rest-framework 门口的传入请求。

Djagno 通过以下方式(来自 settings.py 中的 LOGGING 部分)提供其请求的日志记录(仅“警告”日志级别及以上):

'django.request': {
        'handlers': ['mail_admins'],
        'level': 'ERROR',
        'propagate': False,
 },

我希望实现这样的目标(注意:日志级别为 DEBUG):

'rest_framework.request': {
        'handlers': ['logfile'],
        'level': 'DEBUG',
        'propagate': False,
 },

有没有一种方法可以做到这一点将记录器嵌入到 DRF 的源代码中?
我不知道的 DRF 中是否有某种“日志记录后端”选项?

【问题讨论】:

    标签: python django django-rest-framework


    【解决方案1】:

    我制作了一个通用的RequestLogMiddleware,可以使用decorator_from_middleware 连接到任何Django View

    request_log/middleware.py

    ​​>
    import socket
    import time
    
    
    class RequestLogMiddleware(object):
        def process_request(self, request):
            request.start_time = time.time()
    
        def process_response(self, request, response):
    
            if response['content-type'] == 'application/json':
                if getattr(response, 'streaming', False):
                    response_body = '<<<Streaming>>>'
                else:
                    response_body = response.content
            else:
                response_body = '<<<Not JSON>>>'
    
            log_data = {
                'user': request.user.pk,
    
                'remote_address': request.META['REMOTE_ADDR'],
                'server_hostname': socket.gethostname(),
    
                'request_method': request.method,
                'request_path': request.get_full_path(),
                'request_body': request.body,
    
                'response_status': response.status_code,
                'response_body': response_body,
    
                'run_time': time.time() - request.start_time,
            }
    
            # save log_data in some way
    
            return response
    

    request_log/mixins.py

    ​​>
    from django.utils.decorators import decorator_from_middleware
    
    from .middleware import RequestLogMiddleware
    
    
    class RequestLogViewMixin(object):
        """
        Adds RequestLogMiddleware to any Django View by overriding as_view.
        """
    
        @classmethod
        def as_view(cls, *args, **kwargs):
            view = super(RequestLogViewMixin, cls).as_view(*args, **kwargs)
            view = decorator_from_middleware(RequestLogMiddleware)(view)
            return view
    

    my_django_rest_api/views.py

    ​​>
    from rest_framework import generics
    
    from ...request_log.mixins import RequestLogViewMixin
    
    class SomeListView(
        RequestLogViewMixin,
        generics.ListAPIView
    ):
        ...
    

    【讨论】:

    • 这个兼容python3吗?谢谢!
    • 如果你想记录每个请求怎么办?使用这种方法,几乎​​每个视图都必须子类化。这不违反 DRY 原则吗?没有通用的方法吗?
    • @Darwesh 这个解决方案的重点是只记录特定的视图。但是将其作为中间件实现的全部意义在于,您只需一行配置即可轻松地在全局范围内安装它们。
    • 如何将它添加到我的应用程序中?
    • 在当前版本的django(1.11.13)中,无法在中间件中访问request.POST:django.http.request.RawPostDataException: You cannot access body after reading from request's data stream
    【解决方案2】:

    重写 APIView.initial() 方法以自己添加日志记录。

    调度方法

    以下方法由视图的 .dispatch() 方法直接调用。它们执行在调用处理程序方法之前或之后需要发生的任何操作,例如 .get()、.post()、put()、patch() 和 .delete()。

    .initial(self, request, *args, **kwargs)
    在调用处理程序方法之前执行需要发生的任何操作。此方法用于强制执行权限和限制,以及执行内容协商。

    【讨论】:

    • 我试过了,但似乎如果请求数据(比如说在 POST 一些损坏的 json 时)没有通过解析器的验证,则 .initial() 方法不是调用。
    • @OrPo - initial() 仍将被调用,如果您尝试登录 request.DATA,您只需要确保捕获并处理可能的 ParseError
    • @TomChristie - 感谢您的回复。由于 ParseError 是在解析器中引发的,我应该使用什么“try”语句在 initial() 中捕获它? (对不起,如果这听起来有点愚蠢 - 我对 Python 很陌生 :))
    • @TomChristie 这是一个进步,但在这种情况下,日志数据将是字符串'[Invalid data in request]',或者如果我选择记录异常,json.loads() 会产生任何错误信息。我需要的是在解析之前记录到达解析器的实际(格式错误的)JSON。
    • 当我尝试访问request.content 时,我得到AttributeError: 'WSGIRequest' object has no attribute 'content'
    【解决方案3】:

    制作了一个自定义中间件:

    import logging
    import time
    logger = logging.getLogger(__name__)
    
    class APILogMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response
        
        def __call__(self, request):
            start_time = time.time()
            response = self.get_response(request)
            duration = time.time() - start_time
            response_ms = duration * 1000
            user = str(getattr(request, 'user', ''))
            method = str(getattr(request, 'method', '')).upper()
            status_code = str(getattr(response, 'status_code', ''))
            request_path = str(getattr(request, 'path', ''))
            if status_code == '200' and response_ms > 2000:
                logger.info({
                             "message": "*****SLOW RESPONSE****",
                             "path": request_path,
                             "response_time": str(response_ms) + " ms",
                             "method": method,
                             "user": user,
                             "status_code": status_code
                             })
            return response
    

    这会记录所有需要超过 2 秒才能返回响应的 API。 只需在 settings.py 中添加 MIDDLEWARE = ["path.to.APILogMiddleware"] 的完整路径

    【讨论】:

      【解决方案4】:

      这是我当前的解决方案,用于获取日志中的每个请求/响应。我创建了一个与旧中间件(Django

      import logging
      from django.utils.deprecation import MiddlewareMixin
      
      _logger = logging.getLogger(__name__)
      
      class LogRestMiddleware(MiddlewareMixin):
          """Middleware to log every request/response.
          Is not triggered when the request/response is managed using the cache
          """
      
          def _log_request(self, request):
              """Log the request"""
              user = str(getattr(request, 'user', ''))
              method = str(getattr(request, 'method', '')).upper()
              request_path = str(getattr(request, 'path', ''))
              query_params = str(["%s: %s" %(k,v) for k, v in request.GET.items()])
              query_params = query_params if query_params else ''
      
              _logger.debug("req: (%s) [%s] %s %s", user, method, request_path, query_params)
      
          def _log_response(self, request, response):
              """Log the response using values from the request"""
              user = str(getattr(request, 'user', ''))
              method = str(getattr(request, 'method', '')).upper()
              status_code = str(getattr(response, 'status_code', ''))
              status_text = str(getattr(response, 'status_text', ''))
              request_path = str(getattr(request, 'path', ''))
              size = str(len(response.content))
      
              _logger.debug("res: (%s) [%s] %s - %s (%s / %s)", user, method, request_path, status_code, status_text, size)
      
          def process_response(self, request, response):
              """Method call when the middleware is used in the `MIDDLEWARE_CLASSES` option in the settings. Django < 1.10"""
              self._log_request(request)
              self._log_response(request, response)
              return response
      
          def __call__(self, request):
              """Method call when the middleware is used in the `MIDDLEWARE` option in the settings (Django >= 1.10)"""
              self._log_request(request)
              response = self.get_response(request)
              self._log_response(request, response)
              return response
      

      【讨论】:

        【解决方案5】:

        这是来自@Glyn Jackson 答案的代码:

        在中间件/mixin.py中

        class RequestLogMiddleware(object):
        
            def initial(self, request, *args, **kwargs):
                 super(RequestLogMiddleware, self).initial(request, *args, **kwargs)
                 # store/log the request
        

        在视图中:

        class ViewClass(RequestLogMiddleware, generics.RetrieveAPIView):
             ...
        

        【讨论】:

          【解决方案6】:

          在新的 Django 2+ 最好将中间件用作可调用对象,只需在 settings.py 文件的中间件部分将它与您的项目连接(中间件也可以是一个函数,不仅仅是一个类,因为它是一个可调用的对象,旧的 MiddlewareMixin 现在在 Django 的 deprecated 模块中):

          文档中的更多信息:
          https://docs.djangoproject.com/en/2.2/topics/http/middleware/#writing-your-own-middleware

          class UserActivityLogMiddleware:
              def __init__(self, get_response):
                  self.get_response = get_response
          
              def __call__(self, request):
                  print(request.method)    # In this string we catch request object.
                  response = self.get_response(request)
                  return response
          

          【讨论】:

          • 感谢您提供这个干净、简单和优雅的答案。拯救了我的一天!
          【解决方案7】:

          几年后,Rhumbix 推出了这个库,有人尝试过吗?

          https://github.com/Rhumbix/django-request-logging

          MIDDLEWARE = (
          ...,
          'request_logging.middleware.LoggingMiddleware',
          ...,
          )
          

          并在您的应用中配置日志记录:

          LOGGING = {
              'version': 1,
              'disable_existing_loggers': False,
              'handlers': {
                  'console': {
                      'class': 'logging.StreamHandler',
                  },
              },
              'loggers': {
                  'django.request': {
                      'handlers': ['console'],
                      'level': 'DEBUG',  # change debug level as appropiate
                      'propagate': False,
                  },
              },
          }
          

          【讨论】:

          • 正是我正在寻找的解决方案。感谢发帖!
          【解决方案8】:

          我发现最好和最灵活的方法是通过装饰器添加日志记录。我只是将装饰器添加到我想要记录请求的每个函数(post、get)中,而不是作为整体视图类的一部分。对记录的内容进行更多控制。这些装饰器获取传入的请求对象 (arg[1]),然后将请求对象的部分记录到文件中。

          请参阅https://github.com/slogan621/tscharts/commit/39ed479b04b7077f128774d3a203a86d6f68f03e 了解执行此操作的模板(提交显示了将日志记录到我现有的文件日志记录方案中所需的 settings.py 更改,以及装饰器和示例用法)。

          【讨论】:

          • 这是一个非常好的例子,它对我有用,但我建议你在这里重新创建它,以防在不久的将来 github 存储库发生问题时保留它。
          【解决方案9】:

          一种选择是使用 django-requestlogs (https://github.com/Raekkeri/django-requestlogs (我是作者))。默认情况下,它收集请求和响应数据(以及其他基本信息)并且很容易挂接到 Django 的日志系统中。

          【讨论】:

            猜你喜欢
            • 2019-11-22
            • 2019-11-20
            • 1970-01-01
            • 1970-01-01
            • 2023-01-08
            • 2019-12-26
            • 2016-11-06
            • 2018-02-08
            相关资源
            最近更新 更多