Skip to content

Commit 8a1a39e

Browse files
committed
add test cases for FastAPI failsafe handling
1 parent 50120ce commit 8a1a39e

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed

instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1877,3 +1877,107 @@ def test_custom_header_not_present_in_non_recording_span(self):
18771877
self.assertEqual(200, resp.status_code)
18781878
span_list = self.memory_exporter.get_finished_spans()
18791879
self.assertEqual(len(span_list), 0)
1880+
1881+
1882+
class TestTraceableExceptionHandling(TestBase):
1883+
"""Tests to ensure FastAPI exception handlers are only executed once and with a valid context"""
1884+
1885+
def setUp(self):
1886+
super().setUp()
1887+
1888+
self.app = fastapi.FastAPI()
1889+
1890+
otel_fastapi.FastAPIInstrumentor().instrument_app(self.app)
1891+
self.client = TestClient(self.app)
1892+
self.tracer = self.tracer_provider.get_tracer(__name__)
1893+
self.executed = 0
1894+
self.request_trace_id = None
1895+
self.error_trace_id = None
1896+
1897+
def tearDown(self) -> None:
1898+
super().tearDown()
1899+
with self.disable_logging():
1900+
otel_fastapi.FastAPIInstrumentor().uninstrument_app(self.app)
1901+
1902+
def test_error_handler_context(self):
1903+
"""OTEL tracing contexts must be available during error handler execution"""
1904+
1905+
@self.app.exception_handler(Exception)
1906+
async def _(*_):
1907+
self.error_trace_id = (
1908+
trace.get_current_span().get_span_context().trace_id
1909+
)
1910+
1911+
@self.app.get("/foobar")
1912+
async def _():
1913+
self.request_trace_id = (
1914+
trace.get_current_span().get_span_context().trace_id
1915+
)
1916+
raise Exception("Test Exception")
1917+
1918+
try:
1919+
self.client.get(
1920+
"/foobar",
1921+
)
1922+
except Exception:
1923+
pass
1924+
1925+
self.assertIsNotNone(self.request_trace_id)
1926+
self.assertEqual(self.request_trace_id, self.error_trace_id)
1927+
1928+
def test_error_handler_side_effects(self):
1929+
"""FastAPI default exception handlers (aka error handlers) must be executed exactly once per exception"""
1930+
1931+
@self.app.exception_handler(Exception)
1932+
async def _(*_):
1933+
self.executed += 1
1934+
1935+
@self.app.get("/foobar")
1936+
async def _():
1937+
raise Exception("Test Exception")
1938+
1939+
try:
1940+
self.client.get(
1941+
"/foobar",
1942+
)
1943+
except Exception:
1944+
pass
1945+
1946+
self.assertEqual(self.executed, 1)
1947+
1948+
1949+
class TestFailsafeHooks(TestBase):
1950+
"""Tests to ensure FastAPI instrumentation hooks don't tear through"""
1951+
1952+
def setUp(self):
1953+
super().setUp()
1954+
1955+
self.app = fastapi.FastAPI()
1956+
1957+
@self.app.get("/foobar")
1958+
async def _():
1959+
return {"message": "Hello World"}
1960+
1961+
def failing_hook(*_):
1962+
raise Exception("Hook Exception")
1963+
1964+
otel_fastapi.FastAPIInstrumentor().instrument_app(
1965+
self.app,
1966+
server_request_hook=failing_hook,
1967+
client_request_hook=failing_hook,
1968+
client_response_hook=failing_hook,
1969+
)
1970+
self.client = TestClient(self.app)
1971+
1972+
def tearDown(self) -> None:
1973+
super().tearDown()
1974+
with self.disable_logging():
1975+
otel_fastapi.FastAPIInstrumentor().uninstrument_app(self.app)
1976+
1977+
def test_failsafe_hooks(self):
1978+
"""Crashing hooks must not tear through"""
1979+
resp = self.client.get(
1980+
"/foobar",
1981+
)
1982+
1983+
self.assertEqual(200, resp.status_code)

0 commit comments

Comments
 (0)