Skip to content

Commit bd74225

Browse files
committed
refactor: define custom exception handling middleware to avoid duplicate log of traceback.
1 parent 54a6df1 commit bd74225

File tree

2 files changed

+50
-23
lines changed

2 files changed

+50
-23
lines changed

src/functions_framework/aio/__init__.py

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,6 @@
5959
_FUNCTION_STATUS_HEADER_FIELD = "X-Google-Status"
6060
_CRASH = "crash"
6161

62-
63-
async def _crash_handler(request, exc):
64-
logger = logging.getLogger()
65-
tb_lines = traceback.format_exception(type(exc), exc, exc.__traceback__)
66-
tb_text = "".join(tb_lines)
67-
error_msg = (
68-
f"Exception on {request.url.path} [{request.method}]\n{tb_text}".rstrip()
69-
)
70-
71-
logger.error(error_msg)
72-
73-
headers = {_FUNCTION_STATUS_HEADER_FIELD: _CRASH}
74-
return Response("Internal Server Error", status_code=500, headers=headers)
75-
76-
7762
CloudEventFunction = Callable[[CloudEvent], Union[None, Awaitable[None]]]
7863
HTTPFunction = Callable[[Request], Union[HTTPResponse, Awaitable[HTTPResponse]]]
7964

@@ -193,6 +178,45 @@ def _configure_app_execution_id_logging():
193178
)
194179

195180

181+
182+
183+
class ExceptionHandlerMiddleware:
184+
def __init__(self, app):
185+
self.app = app
186+
187+
async def __call__(self, scope, receive, send):
188+
if scope["type"] != "http": # pragma: no cover
189+
await self.app(scope, receive, send)
190+
return
191+
192+
try:
193+
await self.app(scope, receive, send)
194+
except Exception as exc:
195+
logger = logging.getLogger()
196+
tb_lines = traceback.format_exception(type(exc), exc, exc.__traceback__)
197+
tb_text = "".join(tb_lines)
198+
199+
path = scope.get("path", "/")
200+
method = scope.get("method", "GET")
201+
error_msg = f"Exception on {path} [{method}]\n{tb_text}".rstrip()
202+
203+
logger.error(error_msg)
204+
205+
headers = [[b"content-type", b"text/plain"],
206+
[_FUNCTION_STATUS_HEADER_FIELD.encode(), _CRASH.encode()]]
207+
208+
await send({
209+
"type": "http.response.start",
210+
"status": 500,
211+
"headers": headers,
212+
})
213+
await send({
214+
"type": "http.response.body",
215+
"body": b"Internal Server Error",
216+
})
217+
# Don't re-raise to prevent starlette from printing tracebak again
218+
219+
196220
def create_asgi_app(target=None, source=None, signature_type=None):
197221
"""Create an ASGI application for the function.
198222
@@ -267,13 +291,16 @@ def create_asgi_app(target=None, source=None, signature_type=None):
267291
f"Unsupported signature type for ASGI server: {signature_type}"
268292
)
269293

270-
exception_handlers = {
271-
Exception: _crash_handler,
272-
}
273-
app = Starlette(routes=routes, exception_handlers=exception_handlers)
274-
275-
# Apply ASGI middleware for execution ID
276-
app = execution_id.AsgiMiddleware(app)
294+
from starlette.middleware import Middleware
295+
296+
app = Starlette(
297+
debug=False,
298+
routes=routes,
299+
middleware=[
300+
Middleware(ExceptionHandlerMiddleware),
301+
Middleware(execution_id.AsgiMiddleware),
302+
],
303+
)
277304

278305
return app
279306

tests/test_execution_id_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import re
1818

1919
from functools import partial
20-
from unittest.mock import Mock, patch
20+
from unittest.mock import Mock
2121

2222
import pytest
2323

0 commit comments

Comments
 (0)