Skip to content

Commit 0714d9f

Browse files
authored
Fix middleware being patched multiple times when using FastAPI (#1841)
* Fix middleware being patched multiple times when using FastAPI
1 parent 1ac27c8 commit 0714d9f

File tree

1 file changed

+63
-55
lines changed

1 file changed

+63
-55
lines changed

sentry_sdk/integrations/starlette.py

+63-55
Original file line numberDiff line numberDiff line change
@@ -168,62 +168,66 @@ def patch_exception_middleware(middleware_class):
168168
"""
169169
old_middleware_init = middleware_class.__init__
170170

171-
def _sentry_middleware_init(self, *args, **kwargs):
172-
# type: (Any, Any, Any) -> None
173-
old_middleware_init(self, *args, **kwargs)
171+
not_yet_patched = "_sentry_middleware_init" not in str(old_middleware_init)
174172

175-
# Patch existing exception handlers
176-
old_handlers = self._exception_handlers.copy()
173+
if not_yet_patched:
177174

178-
async def _sentry_patched_exception_handler(self, *args, **kwargs):
175+
def _sentry_middleware_init(self, *args, **kwargs):
179176
# type: (Any, Any, Any) -> None
180-
exp = args[0]
181-
182-
is_http_server_error = (
183-
hasattr(exp, "status_code") and exp.status_code >= 500
184-
)
185-
if is_http_server_error:
186-
_capture_exception(exp, handled=True)
187-
188-
# Find a matching handler
189-
old_handler = None
190-
for cls in type(exp).__mro__:
191-
if cls in old_handlers:
192-
old_handler = old_handlers[cls]
193-
break
194-
195-
if old_handler is None:
196-
return
197-
198-
if _is_async_callable(old_handler):
199-
return await old_handler(self, *args, **kwargs)
200-
else:
201-
return old_handler(self, *args, **kwargs)
177+
old_middleware_init(self, *args, **kwargs)
202178

203-
for key in self._exception_handlers.keys():
204-
self._exception_handlers[key] = _sentry_patched_exception_handler
179+
# Patch existing exception handlers
180+
old_handlers = self._exception_handlers.copy()
205181

206-
middleware_class.__init__ = _sentry_middleware_init
182+
async def _sentry_patched_exception_handler(self, *args, **kwargs):
183+
# type: (Any, Any, Any) -> None
184+
exp = args[0]
207185

208-
old_call = middleware_class.__call__
209-
210-
async def _sentry_exceptionmiddleware_call(self, scope, receive, send):
211-
# type: (Dict[str, Any], Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]]) -> None
212-
# Also add the user (that was eventually set by be Authentication middle
213-
# that was called before this middleware). This is done because the authentication
214-
# middleware sets the user in the scope and then (in the same function)
215-
# calls this exception middelware. In case there is no exception (or no handler
216-
# for the type of exception occuring) then the exception bubbles up and setting the
217-
# user information into the sentry scope is done in auth middleware and the
218-
# ASGI middleware will then send everything to Sentry and this is fine.
219-
# But if there is an exception happening that the exception middleware here
220-
# has a handler for, it will send the exception directly to Sentry, so we need
221-
# the user information right now.
222-
# This is why we do it here.
223-
_add_user_to_sentry_scope(scope)
224-
await old_call(self, scope, receive, send)
225-
226-
middleware_class.__call__ = _sentry_exceptionmiddleware_call
186+
is_http_server_error = (
187+
hasattr(exp, "status_code") and exp.status_code >= 500
188+
)
189+
if is_http_server_error:
190+
_capture_exception(exp, handled=True)
191+
192+
# Find a matching handler
193+
old_handler = None
194+
for cls in type(exp).__mro__:
195+
if cls in old_handlers:
196+
old_handler = old_handlers[cls]
197+
break
198+
199+
if old_handler is None:
200+
return
201+
202+
if _is_async_callable(old_handler):
203+
return await old_handler(self, *args, **kwargs)
204+
else:
205+
return old_handler(self, *args, **kwargs)
206+
207+
for key in self._exception_handlers.keys():
208+
self._exception_handlers[key] = _sentry_patched_exception_handler
209+
210+
middleware_class.__init__ = _sentry_middleware_init
211+
212+
old_call = middleware_class.__call__
213+
214+
async def _sentry_exceptionmiddleware_call(self, scope, receive, send):
215+
# type: (Dict[str, Any], Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]]) -> None
216+
# Also add the user (that was eventually set by be Authentication middle
217+
# that was called before this middleware). This is done because the authentication
218+
# middleware sets the user in the scope and then (in the same function)
219+
# calls this exception middelware. In case there is no exception (or no handler
220+
# for the type of exception occuring) then the exception bubbles up and setting the
221+
# user information into the sentry scope is done in auth middleware and the
222+
# ASGI middleware will then send everything to Sentry and this is fine.
223+
# But if there is an exception happening that the exception middleware here
224+
# has a handler for, it will send the exception directly to Sentry, so we need
225+
# the user information right now.
226+
# This is why we do it here.
227+
_add_user_to_sentry_scope(scope)
228+
await old_call(self, scope, receive, send)
229+
230+
middleware_class.__call__ = _sentry_exceptionmiddleware_call
227231

228232

229233
def _add_user_to_sentry_scope(scope):
@@ -268,12 +272,16 @@ def patch_authentication_middleware(middleware_class):
268272
"""
269273
old_call = middleware_class.__call__
270274

271-
async def _sentry_authenticationmiddleware_call(self, scope, receive, send):
272-
# type: (Dict[str, Any], Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]]) -> None
273-
await old_call(self, scope, receive, send)
274-
_add_user_to_sentry_scope(scope)
275+
not_yet_patched = "_sentry_authenticationmiddleware_call" not in str(old_call)
276+
277+
if not_yet_patched:
278+
279+
async def _sentry_authenticationmiddleware_call(self, scope, receive, send):
280+
# type: (Dict[str, Any], Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]]) -> None
281+
await old_call(self, scope, receive, send)
282+
_add_user_to_sentry_scope(scope)
275283

276-
middleware_class.__call__ = _sentry_authenticationmiddleware_call
284+
middleware_class.__call__ = _sentry_authenticationmiddleware_call
277285

278286

279287
def patch_middlewares():

0 commit comments

Comments
 (0)