2025-12-01
This commit is contained in:
@@ -12,7 +12,6 @@ from functools import partial
|
||||
import sentry_sdk
|
||||
from sentry_sdk.api import continue_trace
|
||||
from sentry_sdk.consts import OP
|
||||
|
||||
from sentry_sdk.integrations._asgi_common import (
|
||||
_get_headers,
|
||||
_get_request_data,
|
||||
@@ -25,10 +24,7 @@ from sentry_sdk.integrations._wsgi_common import (
|
||||
from sentry_sdk.sessions import track_session
|
||||
from sentry_sdk.tracing import (
|
||||
SOURCE_FOR_STYLE,
|
||||
TRANSACTION_SOURCE_ROUTE,
|
||||
TRANSACTION_SOURCE_URL,
|
||||
TRANSACTION_SOURCE_COMPONENT,
|
||||
TRANSACTION_SOURCE_CUSTOM,
|
||||
TransactionSource,
|
||||
)
|
||||
from sentry_sdk.utils import (
|
||||
ContextVar,
|
||||
@@ -45,7 +41,6 @@ from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
@@ -105,6 +100,7 @@ class SentryAsgiMiddleware:
|
||||
mechanism_type="asgi", # type: str
|
||||
span_origin="manual", # type: str
|
||||
http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: Tuple[str, ...]
|
||||
asgi_version=None, # type: Optional[int]
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
@@ -143,10 +139,32 @@ class SentryAsgiMiddleware:
|
||||
self.app = app
|
||||
self.http_methods_to_capture = http_methods_to_capture
|
||||
|
||||
if _looks_like_asgi3(app):
|
||||
self.__call__ = self._run_asgi3 # type: Callable[..., Any]
|
||||
else:
|
||||
self.__call__ = self._run_asgi2
|
||||
if asgi_version is None:
|
||||
if _looks_like_asgi3(app):
|
||||
asgi_version = 3
|
||||
else:
|
||||
asgi_version = 2
|
||||
|
||||
if asgi_version == 3:
|
||||
self.__call__ = self._run_asgi3
|
||||
elif asgi_version == 2:
|
||||
self.__call__ = self._run_asgi2 # type: ignore
|
||||
|
||||
def _capture_lifespan_exception(self, exc):
|
||||
# type: (Exception) -> None
|
||||
"""Capture exceptions raise in application lifespan handlers.
|
||||
|
||||
The separate function is needed to support overriding in derived integrations that use different catching mechanisms.
|
||||
"""
|
||||
return _capture_exception(exc=exc, mechanism_type=self.mechanism_type)
|
||||
|
||||
def _capture_request_exception(self, exc):
|
||||
# type: (Exception) -> None
|
||||
"""Capture exceptions raised in incoming request handlers.
|
||||
|
||||
The separate function is needed to support overriding in derived integrations that use different catching mechanisms.
|
||||
"""
|
||||
return _capture_exception(exc=exc, mechanism_type=self.mechanism_type)
|
||||
|
||||
def _run_asgi2(self, scope):
|
||||
# type: (Any) -> Any
|
||||
@@ -161,7 +179,7 @@ class SentryAsgiMiddleware:
|
||||
return await self._run_app(scope, receive, send, asgi_version=3)
|
||||
|
||||
async def _run_app(self, scope, receive, send, asgi_version):
|
||||
# type: (Any, Any, Any, Any, int) -> Any
|
||||
# type: (Any, Any, Any, int) -> Any
|
||||
is_recursive_asgi_middleware = _asgi_middleware_applied.get(False)
|
||||
is_lifespan = scope["type"] == "lifespan"
|
||||
if is_recursive_asgi_middleware or is_lifespan:
|
||||
@@ -172,7 +190,7 @@ class SentryAsgiMiddleware:
|
||||
return await self.app(scope, receive, send)
|
||||
|
||||
except Exception as exc:
|
||||
_capture_exception(exc, mechanism_type=self.mechanism_type)
|
||||
self._capture_lifespan_exception(exc)
|
||||
raise exc from None
|
||||
|
||||
_asgi_middleware_applied.set(True)
|
||||
@@ -195,8 +213,8 @@ class SentryAsgiMiddleware:
|
||||
|
||||
method = scope.get("method", "").upper()
|
||||
transaction = None
|
||||
if method in self.http_methods_to_capture:
|
||||
if ty in ("http", "websocket"):
|
||||
if ty in ("http", "websocket"):
|
||||
if ty == "websocket" or method in self.http_methods_to_capture:
|
||||
transaction = continue_trace(
|
||||
_get_headers(scope),
|
||||
op="{}.server".format(ty),
|
||||
@@ -204,37 +222,26 @@ class SentryAsgiMiddleware:
|
||||
source=transaction_source,
|
||||
origin=self.span_origin,
|
||||
)
|
||||
logger.debug(
|
||||
"[ASGI] Created transaction (continuing trace): %s",
|
||||
transaction,
|
||||
)
|
||||
else:
|
||||
transaction = Transaction(
|
||||
op=OP.HTTP_SERVER,
|
||||
name=transaction_name,
|
||||
source=transaction_source,
|
||||
origin=self.span_origin,
|
||||
)
|
||||
logger.debug(
|
||||
"[ASGI] Created transaction (new): %s", transaction
|
||||
)
|
||||
|
||||
transaction.set_tag("asgi.type", ty)
|
||||
logger.debug(
|
||||
"[ASGI] Set transaction name and source on transaction: '%s' / '%s'",
|
||||
transaction.name,
|
||||
transaction.source,
|
||||
else:
|
||||
transaction = Transaction(
|
||||
op=OP.HTTP_SERVER,
|
||||
name=transaction_name,
|
||||
source=transaction_source,
|
||||
origin=self.span_origin,
|
||||
)
|
||||
|
||||
with (
|
||||
if transaction:
|
||||
transaction.set_tag("asgi.type", ty)
|
||||
|
||||
transaction_context = (
|
||||
sentry_sdk.start_transaction(
|
||||
transaction,
|
||||
custom_sampling_context={"asgi_scope": scope},
|
||||
)
|
||||
if transaction is not None
|
||||
else nullcontext()
|
||||
):
|
||||
logger.debug("[ASGI] Started transaction: %s", transaction)
|
||||
)
|
||||
with transaction_context:
|
||||
try:
|
||||
|
||||
async def _sentry_wrapped_send(event):
|
||||
@@ -258,7 +265,7 @@ class SentryAsgiMiddleware:
|
||||
scope, receive, _sentry_wrapped_send
|
||||
)
|
||||
except Exception as exc:
|
||||
_capture_exception(exc, mechanism_type=self.mechanism_type)
|
||||
self._capture_request_exception(exc)
|
||||
raise exc from None
|
||||
finally:
|
||||
_asgi_middleware_applied.set(False)
|
||||
@@ -270,13 +277,18 @@ class SentryAsgiMiddleware:
|
||||
event["request"] = deepcopy(request_data)
|
||||
|
||||
# Only set transaction name if not already set by Starlette or FastAPI (or other frameworks)
|
||||
already_set = event["transaction"] != _DEFAULT_TRANSACTION_NAME and event[
|
||||
"transaction_info"
|
||||
].get("source") in [
|
||||
TRANSACTION_SOURCE_COMPONENT,
|
||||
TRANSACTION_SOURCE_ROUTE,
|
||||
TRANSACTION_SOURCE_CUSTOM,
|
||||
]
|
||||
transaction = event.get("transaction")
|
||||
transaction_source = (event.get("transaction_info") or {}).get("source")
|
||||
already_set = (
|
||||
transaction is not None
|
||||
and transaction != _DEFAULT_TRANSACTION_NAME
|
||||
and transaction_source
|
||||
in [
|
||||
TransactionSource.COMPONENT,
|
||||
TransactionSource.ROUTE,
|
||||
TransactionSource.CUSTOM,
|
||||
]
|
||||
)
|
||||
if not already_set:
|
||||
name, source = self._get_transaction_name_and_source(
|
||||
self.transaction_style, asgi_scope
|
||||
@@ -284,12 +296,6 @@ class SentryAsgiMiddleware:
|
||||
event["transaction"] = name
|
||||
event["transaction_info"] = {"source": source}
|
||||
|
||||
logger.debug(
|
||||
"[ASGI] Set transaction name and source in event_processor: '%s' / '%s'",
|
||||
event["transaction"],
|
||||
event["transaction_info"]["source"],
|
||||
)
|
||||
|
||||
return event
|
||||
|
||||
# Helper functions.
|
||||
@@ -313,7 +319,7 @@ class SentryAsgiMiddleware:
|
||||
name = transaction_from_function(endpoint) or ""
|
||||
else:
|
||||
name = _get_url(asgi_scope, "http" if ty == "http" else "ws", host=None)
|
||||
source = TRANSACTION_SOURCE_URL
|
||||
source = TransactionSource.URL
|
||||
|
||||
elif transaction_style == "url":
|
||||
# FastAPI includes the route object in the scope to let Sentry extract the
|
||||
@@ -325,11 +331,11 @@ class SentryAsgiMiddleware:
|
||||
name = path
|
||||
else:
|
||||
name = _get_url(asgi_scope, "http" if ty == "http" else "ws", host=None)
|
||||
source = TRANSACTION_SOURCE_URL
|
||||
source = TransactionSource.URL
|
||||
|
||||
if name is None:
|
||||
name = _DEFAULT_TRANSACTION_NAME
|
||||
source = TRANSACTION_SOURCE_ROUTE
|
||||
source = TransactionSource.ROUTE
|
||||
return name, source
|
||||
|
||||
return name, source
|
||||
|
||||
Reference in New Issue
Block a user