Skip to content

Commit

Permalink
Emit ERROR message when app handles exception
Browse files Browse the repository at this point in the history
When a FastAPI application built from a MLflow model handles an error,
emit a log message at `ERROR` level.

Relates to issue #15
  • Loading branch information
bloomonkey committed Nov 8, 2023
1 parent b9a809a commit 0c92310
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions fastapi_mlflow/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Copyright (C) 2022, Auto Trader UK
"""
import logging
from inspect import signature

from fastapi import FastAPI, Request
Expand All @@ -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
Expand All @@ -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(),
Expand Down
6 changes: 5 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
21 changes: 20 additions & 1 deletion tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

0 comments on commit 0c92310

Please sign in to comment.