Search code examples
djangodjango-rest-frameworkdjango-signals

Django - Log response for each request


I have an API built using Django framework . I have to log the following datapoints for each time a request hits the API.

  1. Request Method
  2. Request Path
  3. Request object
  4. Response Status code
  5. Response Latency (if possible)

I tried using the request_finished signal but it does not bring the response object with it. How can I accomplish this?


Solution

  • This is a job for Django Middleware: https://docs.djangoproject.com/en/4.0/topics/http/middleware/

    When you install "a middleware" you get opportunities to intervene before, during and after request processing, for all requests.

    As described in the documentation link, you need to create a Middleware class and then reference it in your MIDDLEWARE list in the settings.

    The class based Middleware is simple enough, but more flexible (easily holds state and has it's own methods). Something like this:

    class LogRequestsMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
            start = time.perf_counter()
            response = self.get_response(request)
            end = time.perf_counter()
            self.log(request, start, end)
            return response
    
        def log(self, request, start, end):
            # log request.method, request.path_info, etc.
            # log end - start for duration
    

    There are other hooks in this setup but you shouldn't need them. Even if there's an uncaught exception, Django will process it and still return a response object (from get_response) for you to read.

    Bear in mind this is not the same request object you get in a view. It's an instance of django.core.handlers.wsgi.WSGIRequest, but those values you are looking for should all be there.

    To calculate latency you can take values from time.perf_counter() both before and after the request is processed in your __call__ method, and the difference is the duration:

    elapsed = time.perf_counter()  # Includes time elapsed during sleep system-wide
    duration = time.process_time()  # Process time not including sleep