Add ability to log all exceptions (#2262)
* Add ability to log all exceptions * Fix linting 🙄 * Remove shorthand * Make `ErrorHandler.log` backwards-compat * Ignore mypy error * Don't store `noisy_exceptions` attribute in app * Added tests * Store noisy exceptions setting in config * Default to not-noisy if config key not available * Add CLI tests for `noisy-exceptions` * Remove debugging line I left in 😅 * Fix tests Co-authored-by: Adam Hopkins <admhpkns@gmail.com>
This commit is contained in:
parent
71cc30e5cd
commit
f0f81ec458
|
@ -96,6 +96,11 @@ def main():
|
||||||
help="number of worker processes [default 1]\n ",
|
help="number of worker processes [default 1]\n ",
|
||||||
)
|
)
|
||||||
parser.add_argument("-d", "--debug", dest="debug", action="store_true")
|
parser.add_argument("-d", "--debug", dest="debug", action="store_true")
|
||||||
|
parser.add_bool_arguments(
|
||||||
|
"--noisy-exceptions",
|
||||||
|
dest="noisy_exceptions",
|
||||||
|
help="print stack traces for all exceptions",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-r",
|
"-r",
|
||||||
"--reload",
|
"--reload",
|
||||||
|
@ -149,6 +154,7 @@ def main():
|
||||||
f"Module is not a Sanic app, it is a {app_type_name}. "
|
f"Module is not a Sanic app, it is a {app_type_name}. "
|
||||||
f"Perhaps you meant {args.module}.app?"
|
f"Perhaps you meant {args.module}.app?"
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.cert is not None or args.key is not None:
|
if args.cert is not None or args.key is not None:
|
||||||
ssl: Optional[Dict[str, Any]] = {
|
ssl: Optional[Dict[str, Any]] = {
|
||||||
"cert": args.cert,
|
"cert": args.cert,
|
||||||
|
@ -165,7 +171,9 @@ def main():
|
||||||
"debug": args.debug,
|
"debug": args.debug,
|
||||||
"access_log": args.access_log,
|
"access_log": args.access_log,
|
||||||
"ssl": ssl,
|
"ssl": ssl,
|
||||||
|
"noisy_exceptions": args.noisy_exceptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.auto_reload:
|
if args.auto_reload:
|
||||||
kwargs["auto_reload"] = True
|
kwargs["auto_reload"] = True
|
||||||
|
|
||||||
|
|
15
sanic/app.py
15
sanic/app.py
|
@ -962,6 +962,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||||
unix: Optional[str] = None,
|
unix: Optional[str] = None,
|
||||||
loop: None = None,
|
loop: None = None,
|
||||||
reload_dir: Optional[Union[List[str], str]] = None,
|
reload_dir: Optional[Union[List[str], str]] = None,
|
||||||
|
noisy_exceptions: Optional[bool] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Run the HTTP Server and listen until keyboard interrupt or term
|
Run the HTTP Server and listen until keyboard interrupt or term
|
||||||
|
@ -994,6 +995,9 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||||
:type access_log: bool
|
:type access_log: bool
|
||||||
:param unix: Unix socket to listen on instead of TCP port
|
:param unix: Unix socket to listen on instead of TCP port
|
||||||
:type unix: str
|
:type unix: str
|
||||||
|
:param noisy_exceptions: Log exceptions that are normally considered
|
||||||
|
to be quiet/silent
|
||||||
|
:type noisy_exceptions: bool
|
||||||
:return: Nothing
|
:return: Nothing
|
||||||
"""
|
"""
|
||||||
if reload_dir:
|
if reload_dir:
|
||||||
|
@ -1032,6 +1036,9 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||||
if access_log is not None:
|
if access_log is not None:
|
||||||
self.config.ACCESS_LOG = access_log
|
self.config.ACCESS_LOG = access_log
|
||||||
|
|
||||||
|
if noisy_exceptions is not None:
|
||||||
|
self.config.NOISY_EXCEPTIONS = noisy_exceptions
|
||||||
|
|
||||||
server_settings = self._helper(
|
server_settings = self._helper(
|
||||||
host=host,
|
host=host,
|
||||||
port=port,
|
port=port,
|
||||||
|
@ -1090,6 +1097,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||||
unix: Optional[str] = None,
|
unix: Optional[str] = None,
|
||||||
return_asyncio_server: bool = False,
|
return_asyncio_server: bool = False,
|
||||||
asyncio_server_kwargs: Dict[str, Any] = None,
|
asyncio_server_kwargs: Dict[str, Any] = None,
|
||||||
|
noisy_exceptions: Optional[bool] = None,
|
||||||
) -> Optional[AsyncioServer]:
|
) -> Optional[AsyncioServer]:
|
||||||
"""
|
"""
|
||||||
Asynchronous version of :func:`run`.
|
Asynchronous version of :func:`run`.
|
||||||
|
@ -1127,6 +1135,9 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||||
:param asyncio_server_kwargs: key-value arguments for
|
:param asyncio_server_kwargs: key-value arguments for
|
||||||
asyncio/uvloop create_server method
|
asyncio/uvloop create_server method
|
||||||
:type asyncio_server_kwargs: dict
|
:type asyncio_server_kwargs: dict
|
||||||
|
:param noisy_exceptions: Log exceptions that are normally considered
|
||||||
|
to be quiet/silent
|
||||||
|
:type noisy_exceptions: bool
|
||||||
:return: AsyncioServer if return_asyncio_server is true, else Nothing
|
:return: AsyncioServer if return_asyncio_server is true, else Nothing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -1137,10 +1148,14 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
|
||||||
protocol = (
|
protocol = (
|
||||||
WebSocketProtocol if self.websocket_enabled else HttpProtocol
|
WebSocketProtocol if self.websocket_enabled else HttpProtocol
|
||||||
)
|
)
|
||||||
|
|
||||||
# if access_log is passed explicitly change config.ACCESS_LOG
|
# if access_log is passed explicitly change config.ACCESS_LOG
|
||||||
if access_log is not None:
|
if access_log is not None:
|
||||||
self.config.ACCESS_LOG = access_log
|
self.config.ACCESS_LOG = access_log
|
||||||
|
|
||||||
|
if noisy_exceptions is not None:
|
||||||
|
self.config.NOISY_EXCEPTIONS = noisy_exceptions
|
||||||
|
|
||||||
server_settings = self._helper(
|
server_settings = self._helper(
|
||||||
host=host,
|
host=host,
|
||||||
port=port,
|
port=port,
|
||||||
|
|
|
@ -27,6 +27,7 @@ DEFAULT_CONFIG = {
|
||||||
"GRACEFUL_SHUTDOWN_TIMEOUT": 15.0, # 15 sec
|
"GRACEFUL_SHUTDOWN_TIMEOUT": 15.0, # 15 sec
|
||||||
"KEEP_ALIVE_TIMEOUT": 5, # 5 seconds
|
"KEEP_ALIVE_TIMEOUT": 5, # 5 seconds
|
||||||
"KEEP_ALIVE": True,
|
"KEEP_ALIVE": True,
|
||||||
|
"NOISY_EXCEPTIONS": False,
|
||||||
"PROXIES_COUNT": None,
|
"PROXIES_COUNT": None,
|
||||||
"REAL_IP_HEADER": None,
|
"REAL_IP_HEADER": None,
|
||||||
"REGISTER": True,
|
"REGISTER": True,
|
||||||
|
@ -51,6 +52,7 @@ class Config(dict):
|
||||||
GRACEFUL_SHUTDOWN_TIMEOUT: float
|
GRACEFUL_SHUTDOWN_TIMEOUT: float
|
||||||
KEEP_ALIVE_TIMEOUT: int
|
KEEP_ALIVE_TIMEOUT: int
|
||||||
KEEP_ALIVE: bool
|
KEEP_ALIVE: bool
|
||||||
|
NOISY_EXCEPTIONS: bool
|
||||||
PROXIES_COUNT: Optional[int]
|
PROXIES_COUNT: Optional[int]
|
||||||
REAL_IP_HEADER: Optional[str]
|
REAL_IP_HEADER: Optional[str]
|
||||||
REGISTER: bool
|
REGISTER: bool
|
||||||
|
|
|
@ -192,7 +192,8 @@ class ErrorHandler:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def log(request, exception):
|
def log(request, exception):
|
||||||
quiet = getattr(exception, "quiet", False)
|
quiet = getattr(exception, "quiet", False)
|
||||||
if quiet is False:
|
noisy = getattr(request.app.config, "NOISY_EXCEPTIONS", False)
|
||||||
|
if quiet is False or noisy is True:
|
||||||
try:
|
try:
|
||||||
url = repr(request.url)
|
url = repr(request.url)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
|
|
@ -23,6 +23,7 @@ async def app_info_dump(app: Sanic, _):
|
||||||
"access_log": app.config.ACCESS_LOG,
|
"access_log": app.config.ACCESS_LOG,
|
||||||
"auto_reload": app.auto_reload,
|
"auto_reload": app.auto_reload,
|
||||||
"debug": app.debug,
|
"debug": app.debug,
|
||||||
|
"noisy_exceptions": app.config.NOISY_EXCEPTIONS,
|
||||||
}
|
}
|
||||||
logger.info(json.dumps(app_data))
|
logger.info(json.dumps(app_data))
|
||||||
|
|
||||||
|
|
|
@ -182,3 +182,21 @@ def test_version(cmd):
|
||||||
version_string = f"Sanic {__version__}; Routing {__routing_version__}\n"
|
version_string = f"Sanic {__version__}; Routing {__routing_version__}\n"
|
||||||
|
|
||||||
assert out == version_string.encode("utf-8")
|
assert out == version_string.encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"cmd,expected",
|
||||||
|
(
|
||||||
|
("--noisy-exceptions", True),
|
||||||
|
("--no-noisy-exceptions", False),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_noisy_exceptions(cmd, expected):
|
||||||
|
command = ["sanic", "fake.server.app", cmd]
|
||||||
|
out, err, exitcode = capture(command)
|
||||||
|
lines = out.split(b"\n")
|
||||||
|
|
||||||
|
app_info = lines[26]
|
||||||
|
info = json.loads(app_info)
|
||||||
|
|
||||||
|
assert info["noisy_exceptions"] is expected
|
||||||
|
|
|
@ -2,10 +2,11 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic, handlers
|
||||||
from sanic.exceptions import Forbidden, InvalidUsage, NotFound, ServerError
|
from sanic.exceptions import Forbidden, InvalidUsage, NotFound, ServerError
|
||||||
from sanic.handlers import ErrorHandler
|
from sanic.handlers import ErrorHandler
|
||||||
from sanic.response import stream, text
|
from sanic.response import stream, text
|
||||||
|
@ -227,3 +228,18 @@ def test_single_arg_exception_handler_notice(exception_handler_app, caplog):
|
||||||
"v22.3, the legacy style lookup method will not work at all."
|
"v22.3, the legacy style lookup method will not work at all."
|
||||||
)
|
)
|
||||||
assert response.status == 400
|
assert response.status == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_handler_noisy_log(exception_handler_app, monkeypatch):
|
||||||
|
err_logger = Mock()
|
||||||
|
monkeypatch.setattr(handlers, "error_logger", err_logger)
|
||||||
|
|
||||||
|
exception_handler_app.config["NOISY_EXCEPTIONS"] = False
|
||||||
|
exception_handler_app.test_client.get("/1")
|
||||||
|
err_logger.exception.assert_not_called()
|
||||||
|
|
||||||
|
exception_handler_app.config["NOISY_EXCEPTIONS"] = True
|
||||||
|
request, _ = exception_handler_app.test_client.get("/1")
|
||||||
|
err_logger.exception.assert_called_with(
|
||||||
|
"Exception occurred while handling uri: %s", repr(request.url)
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user