From afea855df818616be321caa01acc833915542067 Mon Sep 17 00:00:00 2001 From: Livio Ribeiro Date: Mon, 22 Apr 2024 12:18:42 -0300 Subject: [PATCH] improve exception handling on request --- src/selva/ext/data/memcached/__init__.py | 4 +- src/selva/ext/data/memcached/service.py | 11 +++-- src/selva/ext/data/redis/__init__.py | 4 +- src/selva/ext/data/sqlalchemy/__init__.py | 4 +- src/selva/ext/templates/jinja/__init__.py | 4 +- src/selva/web/application.py | 49 +++++++++++++++-------- 6 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/selva/ext/data/memcached/__init__.py b/src/selva/ext/data/memcached/__init__.py index 34d8e42..f2b453a 100644 --- a/src/selva/ext/data/memcached/__init__.py +++ b/src/selva/ext/data/memcached/__init__.py @@ -9,7 +9,9 @@ async def selva_extension(container: Container, settings: Settings): if find_spec("emcache") is None: - raise ModuleNotFoundError("Missing 'emcache'. Install 'selva' with 'memcached' extra.") + raise ModuleNotFoundError( + "Missing 'emcache'. Install 'selva' with 'memcached' extra." + ) for name in settings.data.memcached: service_name = name if name != "default" else None diff --git a/src/selva/ext/data/memcached/service.py b/src/selva/ext/data/memcached/service.py index fc4b519..39710c5 100644 --- a/src/selva/ext/data/memcached/service.py +++ b/src/selva/ext/data/memcached/service.py @@ -2,7 +2,8 @@ from selva.configuration.settings import Settings from selva.di.container import Container -from selva.di.decorator import service as service_decorator, DI_ATTRIBUTE_SERVICE +from selva.di.decorator import DI_ATTRIBUTE_SERVICE +from selva.di.decorator import service as service_decorator from .settings import MemcachedSettings @@ -39,9 +40,13 @@ async def memcached_service( if not di.has(cluster_events, name=name): di.register(cluster_events) - cluster_events_service_name = getattr(cluster_events, DI_ATTRIBUTE_SERVICE).name + cluster_events_service_name = getattr( + cluster_events, DI_ATTRIBUTE_SERVICE + ).name - memcached_options["cluster_events"] = await di.get(cluster_events, name=cluster_events_service_name) + memcached_options["cluster_events"] = await di.get( + cluster_events, name=cluster_events_service_name + ) else: memcached_options = {} diff --git a/src/selva/ext/data/redis/__init__.py b/src/selva/ext/data/redis/__init__.py index 04ba13e..eb2d801 100644 --- a/src/selva/ext/data/redis/__init__.py +++ b/src/selva/ext/data/redis/__init__.py @@ -9,7 +9,9 @@ def selva_extension(container: Container, settings: Settings): if find_spec("redis") is None: - raise ModuleNotFoundError("Missing 'redis'. Install 'selva' with 'redis' extra.") + raise ModuleNotFoundError( + "Missing 'redis'. Install 'selva' with 'redis' extra." + ) for name in settings.data.redis: service_name = name if name != "default" else None diff --git a/src/selva/ext/data/sqlalchemy/__init__.py b/src/selva/ext/data/sqlalchemy/__init__.py index 7507423..308750d 100644 --- a/src/selva/ext/data/sqlalchemy/__init__.py +++ b/src/selva/ext/data/sqlalchemy/__init__.py @@ -10,7 +10,9 @@ def selva_extension(container: Container, settings: Settings): if find_spec("sqlalchemy") is None: - raise ModuleNotFoundError("Missing 'sqlalchemy'. Install 'selva' with 'sqlalchemy' extra.") + raise ModuleNotFoundError( + "Missing 'sqlalchemy'. Install 'selva' with 'sqlalchemy' extra." + ) for name in settings.data.sqlalchemy.connections: service_name = name if name != "default" else None diff --git a/src/selva/ext/templates/jinja/__init__.py b/src/selva/ext/templates/jinja/__init__.py index d5f76dd..fd3f32b 100644 --- a/src/selva/ext/templates/jinja/__init__.py +++ b/src/selva/ext/templates/jinja/__init__.py @@ -7,7 +7,9 @@ async def selva_extension(container: Container, settings: Settings): if find_spec("jinja2") is None: - raise ModuleNotFoundError("Missing 'jinja2'. Install 'selva' with 'jinja' extra.") + raise ModuleNotFoundError( + "Missing 'jinja2'. Install 'selva' with 'jinja' extra." + ) backend = settings.templates.backend diff --git a/src/selva/web/application.py b/src/selva/web/application.py index ddd2793..6ac7b44 100644 --- a/src/selva/web/application.py +++ b/src/selva/web/application.py @@ -1,5 +1,6 @@ import functools import inspect +import traceback import typing from http import HTTPStatus from typing import Any @@ -178,7 +179,7 @@ async def _handle_request(self, scope, receive, send): if handler := await self.di.get( ExceptionHandler[type(err)], optional=True ): - logger.trace( + logger.debug( "Handling exception with handler {}.{}", handler.__class__.__module__, handler.__class__.__qualname__, @@ -186,31 +187,45 @@ async def _handle_request(self, scope, receive, send): await handler.handle_exception(request, err) else: raise - except WebSocketException as err: - await request.websocket.close(err.code, err.reason) except (WebSocketDisconnectError, WebSocketError): logger.exception("WebSocket error") await request.websocket.close() + except WebSocketException as err: + await request.websocket.close(err.code, err.reason) except HTTPException as err: if websocket := request.websocket: - logger.error("WebSocket request raise HTTPException") + logger.exception("WebSocket request raised HTTPException") await websocket.close() + return + + response = request.response + + if cause := err.__cause__: + logger.exception(cause) + stack_trace = "".join(traceback.format_exception(cause)) else: - response = request.response - if response.is_started: - logger.error("Response has already started") - await response.end() - return - - if response.is_finished: - logger.error("Response is finished") - return - - await respond_text(response, str(err), status=err.status) - except Exception as err: + stack_trace = None + + if response.is_started: + logger.error("Response has already started") + await response.end() + return + + if response.is_finished: + logger.error("Response is finished") + return + + if stack_trace: + await respond_text(response, stack_trace, status=err.status) + else: + await respond_status(response, status=err.status) + except Exception: logger.exception("Error processing request") + await respond_text( - request.response, str(err), status=HTTPStatus.INTERNAL_SERVER_ERROR + request.response, + traceback.format_exc(), + status=HTTPStatus.INTERNAL_SERVER_ERROR, ) async def _process_request(self, request: Request):