Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible enhancement: Propagate unhandled exceptions as-is and format in default Django handlers #91

Open
sshishov opened this issue Oct 6, 2024 · 1 comment

Comments

@sshishov
Copy link

sshishov commented Oct 6, 2024

Hi dear maintainer,

We are using this library for our project and it is amazing.
The only thing we have noticed (we are using structlog + django-structlog + structlog-sentry), the unhandled exceptions are not properly propagated and therefore handled (some libraries are using process_exception inside middleware to "add" something useful or to log something outside.

Currently when unhandled exception is happening, it is automatically "wrapped" into 500 response and returned and therefore no standard Django flow is running to "process" this exception, meaning all middlewares and additional libraries become useless in this case.

Instead it would be great to "propagate" the unhandled exception through all the middlewares and then properly process it inside custom 404 or 500 handlers.

What do you think? Is it possible to implement somehow? Just asking... as I dug into the codebase and and could not find easy route. Unfortunately not all libraries are relying on the signal got_request_exception.

Best regards,
Sergei

@ghazi-git
Copy link
Owner

Honestly, the idea is interesting especially that it allows the middleware to also process the exception. However, this is an exception handler for DRF specifically.

Still, here's how you can work around the issue you described:

  1. create a custom exception handler
from drf_standardized_errors.handler import ExceptionHandler

class CustomExceptionHandler(ExceptionHandler):
    def should_not_handle(self, exc: Exception) -> bool:
        is_drf_exception = isinstance(exc, APIException)
        if not is_drf_exception:
            # attach the context to the exception so we can use it later in the django's handler500.
            # Also, serves as a way to identify exceptions that came from a drf view
            exc.drf_handler_context = self.context
        # handle drf exceptions only. This restores the behavior of the original drf exception handler
        # and propagates any other exceptions
        return not is_drf_exception

then update the settings DRF_STANDARDIZED_ERRORS = {"EXCEPTION_HANDLER_CLASS": "path.to.CustomExceptionHandler"}

  1. define the custom handler500
import sys
from django.http import JsonResponse
from django.views.defaults import server_error
from somewhere import CustomExceptionHandler

def error_handler(request):
    _, exc, __ = sys.exc_info()
    if context := getattr(exc, "drf_handler_context", None):
        handler = CustomExceptionHandler(exc, context)
        exc = handler.convert_unhandled_exceptions(exc)
        data = handler.format_exception(exc)
        return JsonResponse(data, status=500)
    else:
        # for exceptions raised in a non-drf view, use the default django error handler
        return server_error(request)

then set handler500 = "path.to.error_handler" in your urls.py

  1. That's it, you should be good to go: the exception is no longer handled at the drf view level, rather it will propagate up the middleware chain until it gets handled there or reaches handler500. Also, keep in mind that when debug is true, handler500 is not called, rather you'll see the yellow screen.

Let me know if that works for your setup, as I only tested this without structlog

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants