Skip to content

Commit 116f2e0

Browse files
authored
Implement /__error__ endpoint (#123)
* Implement /__error__ endpoint * Pin setuptools * Fix fastapi tests * Pin alembic
1 parent 7297801 commit 116f2e0

File tree

13 files changed

+87
-4
lines changed

13 files changed

+87
-4
lines changed

src/dockerflow/django/middleware.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class DockerflowMiddleware(MiddlewareMixin):
3232
(re.compile(r"/__version__/?$"), views.version),
3333
(re.compile(r"/__heartbeat__/?$"), views.heartbeat),
3434
(re.compile(r"/__lbheartbeat__/?$"), views.lbheartbeat),
35+
(re.compile(r"/__error__/?$"), views.error),
3536
]
3637

3738
def __init__(self, get_response=None, *args, **kwargs):

src/dockerflow/django/views.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,11 @@ def heartbeat(request):
7575
payload["checks"] = check_results.statuses
7676
payload["details"] = check_results.details
7777
return JsonResponse(payload, status=status_code)
78+
79+
80+
def error(request):
81+
"""
82+
A view that raises an exception, used to test error handling.
83+
"""
84+
logger.error("The __error__ endpoint was called")
85+
raise Exception("This is a test exception from the /__error__ endpoint.")

src/dockerflow/fastapi/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from fastapi import APIRouter
22
from fastapi.routing import APIRoute
33

4-
from .views import heartbeat, lbheartbeat, version
4+
from .views import error, heartbeat, lbheartbeat, version
55

66
router = APIRouter(
77
tags=["Dockerflow"],
88
routes=[
99
APIRoute("/__lbheartbeat__", endpoint=lbheartbeat, methods=["GET", "HEAD"]),
1010
APIRoute("/__heartbeat__", endpoint=heartbeat, methods=["GET", "HEAD"]),
1111
APIRoute("/__version__", endpoint=version, methods=["GET"]),
12+
APIRoute("/__error__", endpoint=error, methods=["GET"]),
1213
],
1314
)
1415
"""This router adds the Dockerflow views."""

src/dockerflow/fastapi/views.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import os
23

34
from fastapi import Request, Response
@@ -6,6 +7,8 @@
67

78
from ..version import get_version
89

10+
logger = logging.getLogger(__name__)
11+
912

1013
def lbheartbeat():
1114
return {"status": "ok"}
@@ -42,3 +45,11 @@ def version(request: Request):
4245
else:
4346
root = "/app"
4447
return get_version(root)
48+
49+
50+
def error(request: Request):
51+
"""
52+
A view that raises an exception, used to test error handling.
53+
"""
54+
logger.error("The __error__ endpoint was called")
55+
raise Exception("This is a test exception from the /__error__ endpoint.")

src/dockerflow/flask/app.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ def init_app(self, app):
177177
("/__version__", "version", self._version_view),
178178
("/__heartbeat__", "heartbeat", self._heartbeat_view),
179179
("/__lbheartbeat__", "lbheartbeat", self._lbheartbeat_view),
180+
("/__error__", "error", self._error_view),
180181
):
181182
self._blueprint.add_url_rule(*view)
182183
self._blueprint.before_app_request(self._before_request)
@@ -346,6 +347,13 @@ def render(status_code):
346347
heartbeat_failed.send(self, level=check_results.level)
347348
raise HeartbeatFailure(response=render(status_code))
348349

350+
def _error_view(self):
351+
"""
352+
A view that raises an exception, used to test error handling.
353+
"""
354+
self.logger.error("The __error__ endpoint was called")
355+
raise Exception("This is a test exception from the /__error__ endpoint.")
356+
349357
def version_callback(self, func):
350358
"""
351359
A decorator to optionally register a new Dockerflow version callback

src/dockerflow/sanic/app.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ def init_app(self, app):
130130
("/__version__", "version", self._version_view),
131131
("/__heartbeat__", "heartbeat", self._heartbeat_view),
132132
("/__lbheartbeat__", "lbheartbeat", self._lbheartbeat_view),
133+
("/__error__", "error", self._error_view),
133134
):
134135
app.add_route(handler, uri, name="dockerflow." + name)
135136
app.middleware("request")(extract_request_id)
@@ -200,6 +201,13 @@ async def _version_view(self, request):
200201
else:
201202
return response.json(version_json)
202203

204+
async def _error_view(self, request):
205+
"""
206+
A view that raises an exception, used to test error handling.
207+
"""
208+
self.logger.error("The __error__ endpoint was called")
209+
raise Exception("This is a test exception from the /__error__ endpoint.")
210+
203211
async def _lbheartbeat_view(self, request):
204212
"""
205213
Lets the load balancer know the application is running and available.

tests/constraints/flask-2.0.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Flask<2.1
22
Werkzeug<2.1.0
33
SQLAlchemy<=1.4
4-
Flask-SQLAlchemy<3.0
4+
Flask-SQLAlchemy<3.0
5+
alembic<1.7

tests/constraints/flask-2.1.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Flask<2.2
22
SQLAlchemy<=1.4
33
Flask-SQLAlchemy<3.0
4-
Werkzeug<3
4+
Werkzeug<3
5+
alembic<1.7

tests/django/test_django.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,17 @@ def test_lbheartbeat_makes_no_db_queries(dockerflow_middleware, rf):
134134
assert len(queries) == 0
135135

136136

137+
def test_error_returns_500_and_logs_error(dockerflow_middleware, rf, caplog):
138+
request = rf.get("/__error__")
139+
with caplog.at_level(logging.INFO, logger="dockerflow.django"):
140+
with pytest.raises(Exception, match="__error__ endpoint"):
141+
dockerflow_middleware.process_request(request)
142+
assert len(caplog.records) == 1
143+
record = caplog.records[0]
144+
assert record.getMessage() == "The __error__ endpoint was called"
145+
assert record.levelno == logging.ERROR
146+
147+
137148
@pytest.mark.django_db()
138149
def test_redis_check(client, settings):
139150
settings.DOCKERFLOW_CHECKS = ["dockerflow.django.checks.check_redis_connected"]

tests/fastapi/test_fastapi.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,3 +262,14 @@ def return_error():
262262

263263
response = client.get("/__heartbeat__")
264264
assert response.json()["checks"]["my_check_name"]
265+
266+
267+
def test_error_returns_500_and_logs_error(app, caplog):
268+
client = TestClient(app, raise_server_exceptions=False)
269+
response = client.get("/__error__")
270+
assert response.status_code == 500
271+
assert len(caplog.records) >= 1
272+
record = caplog.records[0]
273+
assert record.name == "dockerflow.fastapi.views"
274+
assert record.getMessage() == "The __error__ endpoint was called"
275+
assert record.levelno == logging.ERROR

0 commit comments

Comments
 (0)