Add a new exception signal for ALL exceptions raised anywhere in application (#2724)
This commit is contained in:
parent
11a0b15194
commit
976da69e79
|
@ -773,6 +773,10 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta):
|
||||||
:raises ServerError: response 500
|
:raises ServerError: response 500
|
||||||
"""
|
"""
|
||||||
response = None
|
response = None
|
||||||
|
await self.dispatch(
|
||||||
|
"server.lifecycle.exception",
|
||||||
|
context={"exception": exception},
|
||||||
|
)
|
||||||
await self.dispatch(
|
await self.dispatch(
|
||||||
"http.lifecycle.exception",
|
"http.lifecycle.exception",
|
||||||
inline=True,
|
inline=True,
|
||||||
|
|
|
@ -6,7 +6,9 @@ from sanic.errorpages import BaseRenderer, TextRenderer, exception_response
|
||||||
from sanic.exceptions import ServerError
|
from sanic.exceptions import ServerError
|
||||||
from sanic.log import error_logger
|
from sanic.log import error_logger
|
||||||
from sanic.models.handler_types import RouteHandler
|
from sanic.models.handler_types import RouteHandler
|
||||||
|
from sanic.request.types import Request
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
|
from sanic.response.types import HTTPResponse
|
||||||
|
|
||||||
|
|
||||||
class ErrorHandler:
|
class ErrorHandler:
|
||||||
|
@ -148,7 +150,7 @@ class ErrorHandler:
|
||||||
return text("An error occurred while handling an error", 500)
|
return text("An error occurred while handling an error", 500)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def default(self, request, exception):
|
def default(self, request: Request, exception: Exception) -> HTTPResponse:
|
||||||
"""
|
"""
|
||||||
Provide a default behavior for the objects of :class:`ErrorHandler`.
|
Provide a default behavior for the objects of :class:`ErrorHandler`.
|
||||||
If a developer chooses to extent the :class:`ErrorHandler` they can
|
If a developer chooses to extent the :class:`ErrorHandler` they can
|
||||||
|
|
|
@ -20,6 +20,7 @@ class Event(Enum):
|
||||||
SERVER_INIT_BEFORE = "server.init.before"
|
SERVER_INIT_BEFORE = "server.init.before"
|
||||||
SERVER_SHUTDOWN_AFTER = "server.shutdown.after"
|
SERVER_SHUTDOWN_AFTER = "server.shutdown.after"
|
||||||
SERVER_SHUTDOWN_BEFORE = "server.shutdown.before"
|
SERVER_SHUTDOWN_BEFORE = "server.shutdown.before"
|
||||||
|
SERVER_LIFECYCLE_EXCEPTION = "server.lifecycle.exception"
|
||||||
HTTP_LIFECYCLE_BEGIN = "http.lifecycle.begin"
|
HTTP_LIFECYCLE_BEGIN = "http.lifecycle.begin"
|
||||||
HTTP_LIFECYCLE_COMPLETE = "http.lifecycle.complete"
|
HTTP_LIFECYCLE_COMPLETE = "http.lifecycle.complete"
|
||||||
HTTP_LIFECYCLE_EXCEPTION = "http.lifecycle.exception"
|
HTTP_LIFECYCLE_EXCEPTION = "http.lifecycle.exception"
|
||||||
|
@ -43,6 +44,7 @@ RESERVED_NAMESPACES = {
|
||||||
Event.SERVER_INIT_BEFORE.value,
|
Event.SERVER_INIT_BEFORE.value,
|
||||||
Event.SERVER_SHUTDOWN_AFTER.value,
|
Event.SERVER_SHUTDOWN_AFTER.value,
|
||||||
Event.SERVER_SHUTDOWN_BEFORE.value,
|
Event.SERVER_SHUTDOWN_BEFORE.value,
|
||||||
|
Event.SERVER_LIFECYCLE_EXCEPTION.value,
|
||||||
),
|
),
|
||||||
"http": (
|
"http": (
|
||||||
Event.HTTP_LIFECYCLE_BEGIN.value,
|
Event.HTTP_LIFECYCLE_BEGIN.value,
|
||||||
|
@ -168,6 +170,16 @@ class SignalRouter(BaseRouter):
|
||||||
elif maybe_coroutine:
|
elif maybe_coroutine:
|
||||||
return maybe_coroutine
|
return maybe_coroutine
|
||||||
return None
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
if self.ctx.app.debug and self.ctx.app.state.verbosity >= 1:
|
||||||
|
error_logger.exception(e)
|
||||||
|
|
||||||
|
if event != Event.SERVER_LIFECYCLE_EXCEPTION.value:
|
||||||
|
await self.dispatch(
|
||||||
|
Event.SERVER_LIFECYCLE_EXCEPTION.value,
|
||||||
|
context={"exception": e},
|
||||||
|
)
|
||||||
|
raise e
|
||||||
finally:
|
finally:
|
||||||
for signal_event in events:
|
for signal_event in events:
|
||||||
signal_event.clear()
|
signal_event.clear()
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
from typing import Optional
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sanic_testing.testing import HOST, PORT
|
from sanic_testing.testing import HOST, PORT
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
from sanic.compat import ctrlc_workaround_for_windows
|
from sanic.compat import ctrlc_workaround_for_windows
|
||||||
from sanic.exceptions import BadRequest
|
from sanic.exceptions import BadRequest, ServerError
|
||||||
from sanic.response import HTTPResponse
|
from sanic.response import HTTPResponse
|
||||||
|
from sanic.signals import Event
|
||||||
|
|
||||||
|
|
||||||
async def stop(app, loop):
|
async def stop(app, loop):
|
||||||
|
@ -148,3 +149,26 @@ def test_signals_with_invalid_invocation(app):
|
||||||
BadRequest, match="Invalid event registration: Missing event name"
|
BadRequest, match="Invalid event registration: Missing event name"
|
||||||
):
|
):
|
||||||
app.listener(stop)
|
app.listener(stop)
|
||||||
|
|
||||||
|
|
||||||
|
def test_signal_server_lifecycle_exception(app: Sanic):
|
||||||
|
trigger: Optional[Exception] = None
|
||||||
|
|
||||||
|
@app.route("/hello")
|
||||||
|
async def hello_route(request):
|
||||||
|
return HTTPResponse()
|
||||||
|
|
||||||
|
@app.signal(Event.SERVER_LIFECYCLE_EXCEPTION)
|
||||||
|
async def test_signal(exception: Exception):
|
||||||
|
nonlocal trigger
|
||||||
|
trigger = exception
|
||||||
|
|
||||||
|
@app.before_server_start
|
||||||
|
async def test_before_server_start(app):
|
||||||
|
raise ServerError("test_before_server_start")
|
||||||
|
|
||||||
|
with pytest.raises(ServerError, match="test_before_server_start"):
|
||||||
|
app.run(single_process=True)
|
||||||
|
|
||||||
|
assert isinstance(trigger, ServerError)
|
||||||
|
assert str(trigger) == "test_before_server_start"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user