mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-02 02:52:18 +08:00
fastapi: fix wrapping of middlewares (#3012)
* fastapi: fix wrapping of middlewares * fix import, super * add test * changelog * lint * lint * fix * ci * fix wip * fix * fix * lint * lint * Exit? * Update test_fastapi_instrumentation.py Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com> * remove break * fix * remove dunders * add test * lint * add endpoint to class * fmt * pr feedback * move type ignores * fix sphinx? * Update CHANGELOG.md * update fastapi versions * fix? * generate * stop passing on user-supplied error handler This prevents potential side effects, such as logging, to be executed more than once per request handler exception. * fix ci Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> * fix ruff Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> * remove unused funcs Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com> * fix lint,ruff Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> * fix changelog Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> * add changelog note Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> * fix conflicts with main Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> --------- Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com> Co-authored-by: Alexander Dorn <ad@not.one> Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com>
This commit is contained in:

committed by
GitHub

parent
dbdff31220
commit
4d6893e8fa
@ -15,6 +15,7 @@
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
import unittest
|
||||
from contextlib import ExitStack
|
||||
from timeit import default_timer
|
||||
from unittest.mock import Mock, call, patch
|
||||
|
||||
@ -183,9 +184,14 @@ class TestBaseFastAPI(TestBase):
|
||||
self._instrumentor = otel_fastapi.FastAPIInstrumentor()
|
||||
self._app = self._create_app()
|
||||
self._app.add_middleware(HTTPSRedirectMiddleware)
|
||||
self._client = TestClient(self._app)
|
||||
self._client = TestClient(self._app, base_url="https://testserver:443")
|
||||
# run the lifespan, initialize the middleware stack
|
||||
# this is more in-line with what happens in a real application when the server starts up
|
||||
self._exit_stack = ExitStack()
|
||||
self._exit_stack.enter_context(self._client)
|
||||
|
||||
def tearDown(self):
|
||||
self._exit_stack.close()
|
||||
super().tearDown()
|
||||
self.env_patch.stop()
|
||||
self.exclude_patch.stop()
|
||||
@ -218,11 +224,19 @@ class TestBaseFastAPI(TestBase):
|
||||
async def _():
|
||||
return {"message": "ok"}
|
||||
|
||||
@app.get("/error")
|
||||
async def _():
|
||||
raise UnhandledException("This is an unhandled exception")
|
||||
|
||||
app.mount("/sub", app=sub_app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
class UnhandledException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TestBaseManualFastAPI(TestBaseFastAPI):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@ -233,6 +247,27 @@ class TestBaseManualFastAPI(TestBaseFastAPI):
|
||||
|
||||
super(TestBaseManualFastAPI, cls).setUpClass()
|
||||
|
||||
def test_fastapi_unhandled_exception(self):
|
||||
"""If the application has an unhandled error the instrumentation should capture that a 500 response is returned."""
|
||||
try:
|
||||
resp = self._client.get("/error")
|
||||
assert (
|
||||
resp.status_code == 500
|
||||
), resp.content # pragma: no cover, for debugging this test if an exception is _not_ raised
|
||||
except UnhandledException:
|
||||
pass
|
||||
else:
|
||||
self.fail("Expected UnhandledException")
|
||||
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 3)
|
||||
span = spans[0]
|
||||
assert span.name == "GET /error http send"
|
||||
assert span.attributes[HTTP_STATUS_CODE] == 500
|
||||
span = spans[2]
|
||||
assert span.name == "GET /error"
|
||||
assert span.attributes[HTTP_TARGET] == "/error"
|
||||
|
||||
def test_sub_app_fastapi_call(self):
|
||||
"""
|
||||
This test is to ensure that a span in case of a sub app targeted contains the correct server url
|
||||
@ -975,6 +1010,10 @@ class TestFastAPIManualInstrumentation(TestBaseManualFastAPI):
|
||||
async def _():
|
||||
return {"message": "ok"}
|
||||
|
||||
@app.get("/error")
|
||||
async def _():
|
||||
raise UnhandledException("This is an unhandled exception")
|
||||
|
||||
app.mount("/sub", app=sub_app)
|
||||
|
||||
return app
|
||||
@ -1137,9 +1176,11 @@ class TestAutoInstrumentation(TestBaseAutoFastAPI):
|
||||
def test_mulitple_way_instrumentation(self):
|
||||
self._instrumentor.instrument_app(self._app)
|
||||
count = 0
|
||||
for middleware in self._app.user_middleware:
|
||||
if middleware.cls is OpenTelemetryMiddleware:
|
||||
app = self._app.middleware_stack
|
||||
while app is not None:
|
||||
if isinstance(app, OpenTelemetryMiddleware):
|
||||
count += 1
|
||||
app = getattr(app, "app", None)
|
||||
self.assertEqual(count, 1)
|
||||
|
||||
def test_uninstrument_after_instrument(self):
|
||||
|
Reference in New Issue
Block a user