Search code examples
djangoexceptionloggingdjango-rest-frameworkmiddleware

How can I catch all 500 and above errors in DRF and log them?


I've looked through much of the SO responses regarding implementing this but I can't seem to find something that isn't deprecated or one that actually works for my use case.

Basically, I have some API methods that return their own status codes. However, if for any reason there is a 500 Server Error or anything above 500 which indicates something strange happened (for example lets say the database connection got timeout), I want that to be logged to a file or e-mailed to an admin.

I have tried using a custom exception handler but not all 500 errors are exceptions.

So I resorted to writing custom middleware, which looked something like this

class CustomApiLoggingMiddleware(MiddlewareMixin):
    def process_response(self, request, response):
        if response.path.startswith('/api/'):
            ...do stuff here

The issue, however, is that there doesn't seem to be a status_code. To be frank I don't really know which response I'm actually getting. There's the 500 response returned by my API intentionally as a result of the API method, and there's the 500 response generated by Django in the backend if some kind of condition isn't met (let's say they sent a request without a proper header)

I tried to generate a 500 response / error intentionally by returning Response(status=status.HTTP_500) in some basic /500 route. Even though this triggers the middleware, there is no status_code that I can use to check whether the response is actually a 500 or not.

I'm not even sure I'm doing this right, but hopefully you understand my intention. I would basically like to blanket catch all uncaught exceptions that can happen as a result of my API calls and log them or send them to an admin. Most calls that return some 40X or whatever are handled in the way that I want already.


Solution

  • I am not entirely familiar with MiddlewareMixin, but the straightforward implementation with a callable class as described in the docs seems to work:

    class ResponseStatusMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
            response = self.get_response(request)
            print(f"Response has status {response.status_code}")
    
            return response
    

    This works fine when the view does return HttpResponse(status=500) with the standard Django HttpResponse. However, when the view raises an exception, the middleware might be bypassed due to Django Rest Framework's exception handling. If get_response is raising an exception, you can handle it by implementing the process_exception hook in your middleware:

    class ResponseStatusMiddleware:
        ...
        def process_exception(self, request, exception):
            ...log your exception...
    

    You could also convert uncaught exceptions to response objects with a custom handler, or, if possible, convert them to DRF's APIException, which is handled automatically.

    Also, the status.HTTP_500 should probably be status.HTTP_500_INTERNAL_SERVER_ERROR (see the DRF documentation).