Problem
titiler.core.errors.exception_handler_factory converts mapped exceptions directly into a JSONResponse without emitting a log record:
def handler(request: Request, exc: Exception):
if status_code == status.HTTP_204_NO_CONTENT:
return Response(content=None, status_code=204)
return JSONResponse(content={"detail": str(exc)}, status_code=status_code)
For concrete exceptions registered through add_exception_handlers, such as RasterioIOError, RasterioError, and RioTilerError, Starlette handles the exception with the registered handler and returns the response. Those exceptions do not continue to the ASGI server's normal unhandled-exception logging path. (The catch-all Exception handler is different — ServerErrorMiddleware re-raises after responding, so servers do log those.)
As a result, production deployments can return 5xx JSON responses without any traceback being emitted by TiTiler itself. Deployers can configure Python logging, but there is currently no log record from these handlers to route.
Proposal
Add a module-level logger in titiler.core.errors and emit an error record before returning 5xx responses:
import logging
logger = logging.getLogger(__name__)
def exception_handler_factory(status_code: int) -> Callable:
def handler(request: Request, exc: Exception):
if status_code >= status.HTTP_500_INTERNAL_SERVER_ERROR:
logger.error(
"Exception mapped to HTTP %s response",
status_code,
exc_info=exc,
)
if status_code == status.HTTP_204_NO_CONTENT:
return Response(content=None, status_code=204)
return JSONResponse(content={"detail": str(exc)}, status_code=status_code)
return handler
This keeps formatting, destinations, and filtering as deployment concerns handled by standard Python logging configuration. It is also narrower than #1190, which discusses structured logging more generally; a future structured logging implementation could still use or replace this logger.
We hit this running titiler.application on AWS Lambda, where 5xx tracebacks never reach CloudWatch Logs and we currently have to wrap the exception handlers ourselves. I would be happy to open a PR with tests if this direction sounds acceptable.
Problem
titiler.core.errors.exception_handler_factoryconverts mapped exceptions directly into aJSONResponsewithout emitting a log record:For concrete exceptions registered through
add_exception_handlers, such asRasterioIOError,RasterioError, andRioTilerError, Starlette handles the exception with the registered handler and returns the response. Those exceptions do not continue to the ASGI server's normal unhandled-exception logging path. (The catch-allExceptionhandler is different —ServerErrorMiddlewarere-raises after responding, so servers do log those.)As a result, production deployments can return 5xx JSON responses without any traceback being emitted by TiTiler itself. Deployers can configure Python logging, but there is currently no log record from these handlers to route.
Proposal
Add a module-level logger in
titiler.core.errorsand emit an error record before returning 5xx responses:This keeps formatting, destinations, and filtering as deployment concerns handled by standard Python logging configuration. It is also narrower than #1190, which discusses structured logging more generally; a future structured logging implementation could still use or replace this logger.
We hit this running
titiler.applicationon AWS Lambda, where 5xx tracebacks never reach CloudWatch Logs and we currently have to wrap the exception handlers ourselves. I would be happy to open a PR with tests if this direction sounds acceptable.