diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b91ad1..0ad0aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Emit error message when an exception is handled by the built application ## [0.6.2] - 2023-08-23 ### Fixed diff --git a/fastapi_mlflow/applications.py b/fastapi_mlflow/applications.py index f049fd7..76f3b99 100644 --- a/fastapi_mlflow/applications.py +++ b/fastapi_mlflow/applications.py @@ -4,6 +4,7 @@ Copyright (C) 2022, Auto Trader UK """ +import logging from inspect import signature from fastapi import FastAPI, Request @@ -16,6 +17,7 @@ def build_app(pyfunc_model: PyFuncModel) -> FastAPI: """Build and return a FastAPI app for the mlflow model.""" + logger = logging.getLogger(__name__) app = FastAPI() predictor = build_predictor(pyfunc_model) response_model = signature(predictor).return_annotation @@ -31,6 +33,8 @@ def build_app(pyfunc_model: PyFuncModel) -> FastAPI: def handle_serialisable_exception( _: Request, exc: DictSerialisableException ) -> ORJSONResponse: + nonlocal logger + logger.exception(exc.message) return ORJSONResponse( status_code=500, content=exc.to_dict(), diff --git a/tests/conftest.py b/tests/conftest.py index 47549e0..1638b43 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -109,10 +109,14 @@ def predict( class ExceptionRaiser(PythonModel): + """A PythonModle that always raises an exception.""" + + ERROR_MESSAGE = "I always raise an error!" + def predict( self, context: PythonModelContext, model_input: pd.DataFrame ) -> pd.DataFrame: - raise ValueError("I always raise an error!") + raise ValueError(self.ERROR_MESSAGE) @pytest.fixture(scope="session") diff --git a/tests/test_application.py b/tests/test_application.py index 998b16d..9d850f2 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -12,7 +12,7 @@ import pytest as pytest from fastapi import FastAPI from fastapi.testclient import TestClient -from mlflow.pyfunc import PyFuncModel # type: ignore +from mlflow.pyfunc import PyFuncModel, PythonModel # type: ignore from fastapi_mlflow.applications import build_app @@ -87,3 +87,22 @@ def test_built_application_handles_model_exceptions( "name": "ValueError", "message": "I always raise an error!", } == response.json() + + +def test_built_application_logs_exceptions( + model_input: pd.DataFrame, + pyfunc_model_value_error: PyFuncModel, + python_model_value_error: PythonModel, + caplog: pytest.LogCaptureFixture, +): + app = build_app(pyfunc_model_value_error) + client = TestClient(app, raise_server_exceptions=False) + df_str = model_input.to_json(orient="records") + request_data = f'{{"data": {df_str}}}' + + _ = client.post("/predictions", content=request_data) + + assert len(caplog.records) >= 1 + log_record = caplog.records[-1] + assert log_record.name == "fastapi_mlflow.applications" + assert log_record.message == python_model_value_error.ERROR_MESSAGE \ No newline at end of file