starlette icon indicating copy to clipboard operation
starlette copied to clipboard

Custom error handler not called by `ServerErrorMiddleware` in debug mode

Open alex-oleshkevich opened this issue 2 years ago • 13 comments

ServerErrorMiddleware ignores handler argument when app is running in debug mode. Instead, it always calls the default one.

from starlette.applications import Starlette
from starlette.responses import Response

def my_handler(request, exc): 
    if request.app.debug:
        return Response('Fail in debug mode')
    return Response('Fail in production mode')

app = Starlette(
    debug=True,
    exception_handlers={Exception: my_handler}
)

This causes us to do workarounds like reimplementing ServerErrorMiddleware on our own just to use an alternate exception handler in debug mode. Though intuitively, you expect exception_handlers for that purpose.

Would it make sense to always call a configured handler when available, and ignore the default one? A possible solution can look like this:

try:
    await self.app(scope, receive, _send)
except Exception as exc:
    request = Request(scope)
    if self.handler is not None:
        if is_async_callable(self.handler):
            response = await self.handler(request, exc)
        else:
            response = await run_in_threadpool(self.handler, request, exc)
    elif self.debug:
        # In debug mode, return traceback responses.
        response = self.debug_response(request, exc)
    else:
        # Use our default 500 error handler.
        response = self.error_response(request, exc)

Let me know if I can PR it.

alex-oleshkevich avatar Aug 10 '22 17:08 alex-oleshkevich

Let me know if I can PR it.

yes, it would be very much appreciated ;)

euri10 avatar Aug 10 '22 18:08 euri10

  • This behavior is wanted, as explained on https://github.com/encode/starlette/pull/1803#issuecomment-1212394367.

Kludex avatar Aug 11 '22 19:08 Kludex

It doesn't make sense to me for debug to lose priority. Like, usually you have your handlers, and to debug, you set the parameter.

Kludex avatar Aug 11 '22 19:08 Kludex

It doesn't make sense to me for debug to lose priority. Like, usually you have your handlers, and to debug, you set the parameter.

Right, that was my rationale here too. For comparison, we've got equivalent behaviour here to Django's 500 views and debug behaviour...

https://docs.djangoproject.com/en/4.0/ref/views/#the-500-server-error-view

tomchristie avatar Aug 12 '22 10:08 tomchristie

@alex-oleshkevich Are you strong on this, or can we close this issue and associated PR?

@tomchristie Would you mind inviting @alex-oleshkevich to the organization?

Kludex avatar Sep 03 '22 11:09 Kludex

@Kludex I think you can close it. However, I still believe that this is a good add-on to the library. The current implementation steals too much control from the developer.

alex-oleshkevich avatar Sep 04 '22 10:09 alex-oleshkevich

that would be nice to have yes

euri10 avatar Sep 05 '22 11:09 euri10

It doesn't make sense to me for debug to lose priority. Like, usually you have your handlers, and to debug, you set the parameter.

Right, that was my rationale here too. For comparison, we've got equivalent behaviour here to Django's 500 views and debug behaviour...

docs.djangoproject.com/en/4.0/ref/views/#the-500-server-error-view

Flask has the same behavior as well: https://flask.palletsprojects.com/en/2.2.x/errorhandling/#unhandled-exceptions

Why would Starlette do something different than those two?

Kludex avatar Sep 05 '22 12:09 Kludex

well 2 reasons, one is that Starlette debugger is very "minimal" compared to what Django and Flask ships by default.

Starception is definitely nicer in every possible way than this default, even superior imho to what Flask provides.

There would be no case if Starlette's debugger would be on par with it.

Starlette wants to keep the maintained codebase small, so "plugins" like this should be usable I think

euri10 avatar Sep 05 '22 12:09 euri10

Starlette wants to keep the maintained codebase small, so "plugins" like this should be usable I think

Correct me if I'm wrong, but... It's usable, and @alex-oleshkevich is not blocked. The package can document for users to do something like:

if debug:
    middleware.append([DebugMiddleware])

Or... Change the DebugMiddleware API to something like: DebugMiddleware(debug=debug) - this doesn't look that good, but there are alternatives.

well 2 reasons, one is that Starlette debugger is very "minimal" compared to what Django and Flask ships by default.

If this gets in, then it will be even minimal, as I don't see why would someone use it anymore. I think the discussion should be different in this case: "is the debug flag useful?".

Kludex avatar Sep 05 '22 12:09 Kludex

yes it's usable, but (don't quote me on this I may remember the stuff badly) it is not the outer layer of the middleware stack as the exception layer should be iirc, so exceptions raised after will be rendered using the starlette debugger template and not the starception one.

https://github.com/encode/starlette/pull/1386 dismissed it, the current debugger in starlette serves no purpose to me in its current form.

euri10 avatar Sep 05 '22 12:09 euri10

yes it's usable, but (don't quote me on this I may remember the stuff badly) it is not the outer layer of the middleware stack as the exception layer should be iirc, so exceptions raised after will be rendered using the starlette debugger template and not the starception one.

Can't the package recommend something like:

middleware = []
if os.getenv("DEBUG"):
    middleware.append([DebugMiddleware])

app = Starlette(middleware=middleware) # Do not use the `debug` flag!

Instead of:

middleware = [DebugMiddleware]

app = Starlette(middleware=middleware, debug=True) 

Kludex avatar Sep 05 '22 12:09 Kludex

Can't the package recommend something like:

Sure, but this looks like a hack

IMO, the perfect solution is to re-use existing functionality of exception handlers or add a new constructor argument to Starlette to override exception handler in ServerErrorMiddleware. Currently, it looks for Exception or 500 error handler.

app = Starlette(middleware=middleware) # Do not use the debug flag!

This is bad advice as application may depend on that flag in different ways. Also, passing the debug flag into multiple places is a cause of errors and confusion. It is already available via app.debug.

alex-oleshkevich avatar Sep 06 '22 10:09 alex-oleshkevich