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

May need better integration with ASGI/WSGI servers #16

Closed
pdonorio opened this issue Jan 9, 2020 · 7 comments
Closed

May need better integration with ASGI/WSGI servers #16

pdonorio opened this issue Jan 9, 2020 · 7 comments

Comments

@pdonorio
Copy link

pdonorio commented Jan 9, 2020

Hello there,
I'd like to use pretty errors in my web development, but I have some issues.

I use the fastapi framework, which runs multi processes through the ASGI uvicorn server. Doesn't matter what I do (import, setup config, usercustomize.py etc.) errors do not get pretty in this context. They do when I run my normal scripts.

Can I ask for help/directions?

@onelivesleft
Copy link
Owner

onelivesleft commented Jan 9, 2020

uvicorn is installing a bunch of loggers, which are handling exceptions. As such, pretty_errors is never getting to see the stack trace - its replacement of sys.excepthook is never being utilized. I'm not sure how you could go about working around this: perhaps you could remove some of the loggers or handlers, or perhaps you would need to patch the uvicorn source to import pretty_errors.

A simple albeit limited fix is to use an older version of pretty_errors: in its original form, pretty_errors did not hook into the exception system (as it correctly now does) - instead, it replaced sys.stderr; scanning the output strings for exception-like data and replacing them. I've just checked and this version does work with uvicorn, though as it's old it does lack a lot of the new features I implemented over the holidays. If you want to try it:

pip install pretty_errors==1.0.10

Example output:
image

@pdonorio
Copy link
Author

Thanks for the quick reply!
I've tried the old version and as you said it works.

I'll stick with it and notify uvicorn developers about the situation
to see if they can help on their side.

Question: I read in here
https://www.uvicorn.org/server-behavior/#server-errors
that "All logging defaults to being written to stdout"
is that the issue?

@onelivesleft
Copy link
Owner

onelivesleft commented Jan 10, 2020

No, it's definitely sending to stderr: you can check by redirecting in the console. It's doesn't work because uvicorn is catching BaseException and controlling the flow from there; pretty-errors never sees a stack trace.

I've re-implemented the old way of doing it to handle this situation: update to 1.2.9 and you can call

pretty_errors.replace_stderr()

It should then work (though with slightly less functionality, as it no longer has access to the stack trace). Let me know how it goes!

@pdonorio
Copy link
Author

update to 1.2.9 and you can call

Ah! Did you patch it already? 😮
There's even a 1.2.10!! 😮

the replace_stderr works, thank you ^_^

@Tiendil
Copy link

Tiendil commented Dec 27, 2020

Hi.

I think the better solution exists for fastapi (and most of the other web frameworks): we can implement custom middleware or exception handler on the layer of the framework.

In that case, the uvicorn will not receive an exception at all and we can freely print it with pretty_errors.

See example for fastapi:

import sys

import fastapi
import pydantic


class Settings(pydantic.BaseSettings):
    use_pretty_errors: bool = False


settings = Settings()


app = fastapi.FastAPI()


@app.get('/test')
async def slices_list():
    y = 1
    x = 0
    return {'result': y / x}


class PrettyErrors:
    __slots__ = ('initialized',)

    def __init__(self):
        self.initialized = False

    def initialize(self):
        import pretty_errors
        pretty_errors.configure(display_locals=True)

        self.initialized = True

    def print_exception(self):
        import pretty_errors

        if not self.initialized:
            self.initialize()

        pretty_errors.excepthook(*sys.exc_info())


pretty_errors_handler = PrettyErrors()


@app.middleware("http")
async def exception_handler(request: fastapi.Request, call_next):

    try:
        return await call_next(request)
    except Exception:
        if settings.use_pretty_errors:
            pretty_errors_handler.print_exception()
        else:
            logging.exception('Unexpected error occured.')

        return fastapi.responses.JSONResponse(content={'message': 'Unexpected error occured. We already fixin it.'},
                                              status_code=fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR)

@onelivesleft
Copy link
Owner

I should have posted some appreciation for this instead of just linking from the readme: thanks @Tiendil!

@Jakobhenningjensen
Copy link

In that case, the uvicorn will not receive an exception at all and we can freely print it with pretty_errors.

Note this does only work in the app i.e if you have some errors outside the app e.g import errors, database-errors on startup etc. then those will not be caugt.

I'm actually having that issue atm, that I want to catch errors/warnings outside the app, but that seems rather impossible

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

4 participants