sentry-sdk 0.18.0__py2.py3-none-any.whl → 2.46.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sentry_sdk/__init__.py +48 -6
- sentry_sdk/_compat.py +64 -56
- sentry_sdk/_init_implementation.py +84 -0
- sentry_sdk/_log_batcher.py +172 -0
- sentry_sdk/_lru_cache.py +47 -0
- sentry_sdk/_metrics_batcher.py +167 -0
- sentry_sdk/_queue.py +81 -19
- sentry_sdk/_types.py +311 -11
- sentry_sdk/_werkzeug.py +98 -0
- sentry_sdk/ai/__init__.py +7 -0
- sentry_sdk/ai/monitoring.py +137 -0
- sentry_sdk/ai/utils.py +144 -0
- sentry_sdk/api.py +409 -67
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +849 -103
- sentry_sdk/consts.py +1389 -34
- sentry_sdk/crons/__init__.py +10 -0
- sentry_sdk/crons/api.py +62 -0
- sentry_sdk/crons/consts.py +4 -0
- sentry_sdk/crons/decorator.py +135 -0
- sentry_sdk/debug.py +12 -15
- sentry_sdk/envelope.py +112 -61
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +442 -386
- sentry_sdk/integrations/__init__.py +228 -58
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +131 -40
- sentry_sdk/integrations/aiohttp.py +221 -72
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +4 -6
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +237 -135
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +13 -18
- sentry_sdk/integrations/aws_lambda.py +233 -80
- sentry_sdk/integrations/beam.py +27 -35
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +91 -69
- sentry_sdk/integrations/celery/__init__.py +529 -0
- sentry_sdk/integrations/celery/beat.py +293 -0
- sentry_sdk/integrations/celery/utils.py +43 -0
- sentry_sdk/integrations/chalice.py +35 -28
- sentry_sdk/integrations/clickhouse_driver.py +177 -0
- sentry_sdk/integrations/cloud_resource_context.py +280 -0
- sentry_sdk/integrations/cohere.py +274 -0
- sentry_sdk/integrations/dedupe.py +32 -8
- sentry_sdk/integrations/django/__init__.py +343 -89
- sentry_sdk/integrations/django/asgi.py +201 -22
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +80 -32
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +69 -2
- sentry_sdk/integrations/django/transactions.py +39 -14
- sentry_sdk/integrations/django/views.py +69 -16
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +19 -13
- sentry_sdk/integrations/executing.py +5 -6
- sentry_sdk/integrations/falcon.py +128 -65
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +114 -75
- sentry_sdk/integrations/gcp.py +67 -36
- sentry_sdk/integrations/gnu_backtrace.py +14 -22
- sentry_sdk/integrations/google_genai/__init__.py +301 -0
- sentry_sdk/integrations/google_genai/consts.py +16 -0
- sentry_sdk/integrations/google_genai/streaming.py +155 -0
- sentry_sdk/integrations/google_genai/utils.py +576 -0
- sentry_sdk/integrations/gql.py +162 -0
- sentry_sdk/integrations/graphene.py +151 -0
- sentry_sdk/integrations/grpc/__init__.py +168 -0
- sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
- sentry_sdk/integrations/grpc/aio/client.py +95 -0
- sentry_sdk/integrations/grpc/aio/server.py +100 -0
- sentry_sdk/integrations/grpc/client.py +91 -0
- sentry_sdk/integrations/grpc/consts.py +1 -0
- sentry_sdk/integrations/grpc/server.py +66 -0
- sentry_sdk/integrations/httpx.py +178 -0
- sentry_sdk/integrations/huey.py +174 -0
- sentry_sdk/integrations/huggingface_hub.py +378 -0
- sentry_sdk/integrations/langchain.py +1132 -0
- sentry_sdk/integrations/langgraph.py +337 -0
- sentry_sdk/integrations/launchdarkly.py +61 -0
- sentry_sdk/integrations/litellm.py +287 -0
- sentry_sdk/integrations/litestar.py +315 -0
- sentry_sdk/integrations/logging.py +261 -85
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +6 -33
- sentry_sdk/integrations/openai.py +725 -0
- sentry_sdk/integrations/openai_agents/__init__.py +61 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
- sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
- sentry_sdk/integrations/openai_agents/utils.py +199 -0
- sentry_sdk/integrations/openfeature.py +35 -0
- sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
- sentry_sdk/integrations/opentelemetry/consts.py +5 -0
- sentry_sdk/integrations/opentelemetry/integration.py +58 -0
- sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
- sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
- sentry_sdk/integrations/otlp.py +82 -0
- sentry_sdk/integrations/pure_eval.py +20 -11
- sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
- sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
- sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
- sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
- sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
- sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
- sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
- sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
- sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
- sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
- sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
- sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
- sentry_sdk/integrations/pymongo.py +214 -0
- sentry_sdk/integrations/pyramid.py +71 -60
- sentry_sdk/integrations/quart.py +237 -0
- sentry_sdk/integrations/ray.py +165 -0
- sentry_sdk/integrations/redis/__init__.py +48 -0
- sentry_sdk/integrations/redis/_async_common.py +116 -0
- sentry_sdk/integrations/redis/_sync_common.py +119 -0
- sentry_sdk/integrations/redis/consts.py +19 -0
- sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- sentry_sdk/integrations/redis/modules/caches.py +118 -0
- sentry_sdk/integrations/redis/modules/queries.py +65 -0
- sentry_sdk/integrations/redis/rb.py +32 -0
- sentry_sdk/integrations/redis/redis.py +69 -0
- sentry_sdk/integrations/redis/redis_cluster.py +107 -0
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
- sentry_sdk/integrations/redis/utils.py +148 -0
- sentry_sdk/integrations/rq.py +62 -52
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +248 -114
- sentry_sdk/integrations/serverless.py +13 -22
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/spark_driver.py +115 -62
- sentry_sdk/integrations/spark/spark_worker.py +42 -50
- sentry_sdk/integrations/sqlalchemy.py +82 -37
- sentry_sdk/integrations/starlette.py +737 -0
- sentry_sdk/integrations/starlite.py +292 -0
- sentry_sdk/integrations/statsig.py +37 -0
- sentry_sdk/integrations/stdlib.py +100 -58
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +142 -38
- sentry_sdk/integrations/tornado.py +68 -53
- sentry_sdk/integrations/trytond.py +15 -20
- sentry_sdk/integrations/typer.py +60 -0
- sentry_sdk/integrations/unleash.py +33 -0
- sentry_sdk/integrations/unraisablehook.py +53 -0
- sentry_sdk/integrations/wsgi.py +126 -125
- sentry_sdk/logger.py +96 -0
- sentry_sdk/metrics.py +81 -0
- sentry_sdk/monitor.py +120 -0
- sentry_sdk/profiler/__init__.py +49 -0
- sentry_sdk/profiler/continuous_profiler.py +730 -0
- sentry_sdk/profiler/transaction_profiler.py +839 -0
- sentry_sdk/profiler/utils.py +195 -0
- sentry_sdk/scope.py +1542 -112
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +152 -210
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +202 -179
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1202 -294
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +693 -189
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1395 -228
- sentry_sdk/worker.py +30 -17
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
- sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
- sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
- sentry_sdk/_functools.py +0 -66
- sentry_sdk/integrations/celery.py +0 -275
- sentry_sdk/integrations/redis.py +0 -103
- sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
- sentry_sdk-0.18.0.dist-info/METADATA +0 -66
- sentry_sdk-0.18.0.dist-info/RECORD +0 -65
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
3
|
+
import warnings
|
|
4
|
+
from collections.abc import Set
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from json import JSONDecodeError
|
|
7
|
+
|
|
8
|
+
import sentry_sdk
|
|
9
|
+
from sentry_sdk.consts import OP
|
|
10
|
+
from sentry_sdk.integrations import (
|
|
11
|
+
DidNotEnable,
|
|
12
|
+
Integration,
|
|
13
|
+
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
|
|
14
|
+
)
|
|
15
|
+
from sentry_sdk.integrations._wsgi_common import (
|
|
16
|
+
DEFAULT_HTTP_METHODS_TO_CAPTURE,
|
|
17
|
+
HttpCodeRangeContainer,
|
|
18
|
+
_is_json_content_type,
|
|
19
|
+
request_body_within_bounds,
|
|
20
|
+
)
|
|
21
|
+
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
|
|
22
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
23
|
+
from sentry_sdk.tracing import (
|
|
24
|
+
SOURCE_FOR_STYLE,
|
|
25
|
+
TransactionSource,
|
|
26
|
+
)
|
|
27
|
+
from sentry_sdk.utils import (
|
|
28
|
+
AnnotatedValue,
|
|
29
|
+
capture_internal_exceptions,
|
|
30
|
+
ensure_integration_enabled,
|
|
31
|
+
event_from_exception,
|
|
32
|
+
parse_version,
|
|
33
|
+
transaction_from_function,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
from typing import TYPE_CHECKING
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from typing import Any, Awaitable, Callable, Container, Dict, Optional, Tuple, Union
|
|
40
|
+
|
|
41
|
+
from sentry_sdk._types import Event, HttpStatusCodeRange
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
import starlette # type: ignore
|
|
45
|
+
from starlette import __version__ as STARLETTE_VERSION
|
|
46
|
+
from starlette.applications import Starlette # type: ignore
|
|
47
|
+
from starlette.datastructures import UploadFile # type: ignore
|
|
48
|
+
from starlette.middleware import Middleware # type: ignore
|
|
49
|
+
from starlette.middleware.authentication import ( # type: ignore
|
|
50
|
+
AuthenticationMiddleware,
|
|
51
|
+
)
|
|
52
|
+
from starlette.requests import Request # type: ignore
|
|
53
|
+
from starlette.routing import Match # type: ignore
|
|
54
|
+
from starlette.types import ASGIApp, Receive, Scope as StarletteScope, Send # type: ignore
|
|
55
|
+
except ImportError:
|
|
56
|
+
raise DidNotEnable("Starlette is not installed")
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
# Starlette 0.20
|
|
60
|
+
from starlette.middleware.exceptions import ExceptionMiddleware # type: ignore
|
|
61
|
+
except ImportError:
|
|
62
|
+
# Startlette 0.19.1
|
|
63
|
+
from starlette.exceptions import ExceptionMiddleware # type: ignore
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Optional dependency of Starlette to parse form data.
|
|
67
|
+
try:
|
|
68
|
+
# python-multipart 0.0.13 and later
|
|
69
|
+
import python_multipart as multipart # type: ignore
|
|
70
|
+
except ImportError:
|
|
71
|
+
# python-multipart 0.0.12 and earlier
|
|
72
|
+
import multipart # type: ignore
|
|
73
|
+
except ImportError:
|
|
74
|
+
multipart = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
_DEFAULT_TRANSACTION_NAME = "generic Starlette request"
|
|
78
|
+
|
|
79
|
+
TRANSACTION_STYLE_VALUES = ("endpoint", "url")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class StarletteIntegration(Integration):
|
|
83
|
+
identifier = "starlette"
|
|
84
|
+
origin = f"auto.http.{identifier}"
|
|
85
|
+
|
|
86
|
+
transaction_style = ""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
transaction_style="url", # type: str
|
|
91
|
+
failed_request_status_codes=_DEFAULT_FAILED_REQUEST_STATUS_CODES, # type: Union[Set[int], list[HttpStatusCodeRange], None]
|
|
92
|
+
middleware_spans=True, # type: bool
|
|
93
|
+
http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: tuple[str, ...]
|
|
94
|
+
):
|
|
95
|
+
# type: (...) -> None
|
|
96
|
+
if transaction_style not in TRANSACTION_STYLE_VALUES:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
"Invalid value for transaction_style: %s (must be in %s)"
|
|
99
|
+
% (transaction_style, TRANSACTION_STYLE_VALUES)
|
|
100
|
+
)
|
|
101
|
+
self.transaction_style = transaction_style
|
|
102
|
+
self.middleware_spans = middleware_spans
|
|
103
|
+
self.http_methods_to_capture = tuple(map(str.upper, http_methods_to_capture))
|
|
104
|
+
|
|
105
|
+
if isinstance(failed_request_status_codes, Set):
|
|
106
|
+
self.failed_request_status_codes = failed_request_status_codes # type: Container[int]
|
|
107
|
+
else:
|
|
108
|
+
warnings.warn(
|
|
109
|
+
"Passing a list or None for failed_request_status_codes is deprecated. "
|
|
110
|
+
"Please pass a set of int instead.",
|
|
111
|
+
DeprecationWarning,
|
|
112
|
+
stacklevel=2,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if failed_request_status_codes is None:
|
|
116
|
+
self.failed_request_status_codes = _DEFAULT_FAILED_REQUEST_STATUS_CODES
|
|
117
|
+
else:
|
|
118
|
+
self.failed_request_status_codes = HttpCodeRangeContainer(
|
|
119
|
+
failed_request_status_codes
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def setup_once():
|
|
124
|
+
# type: () -> None
|
|
125
|
+
version = parse_version(STARLETTE_VERSION)
|
|
126
|
+
|
|
127
|
+
if version is None:
|
|
128
|
+
raise DidNotEnable(
|
|
129
|
+
"Unparsable Starlette version: {}".format(STARLETTE_VERSION)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
patch_middlewares()
|
|
133
|
+
patch_asgi_app()
|
|
134
|
+
patch_request_response()
|
|
135
|
+
|
|
136
|
+
if version >= (0, 24):
|
|
137
|
+
patch_templates()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _enable_span_for_middleware(middleware_class):
|
|
141
|
+
# type: (Any) -> type
|
|
142
|
+
old_call = middleware_class.__call__
|
|
143
|
+
|
|
144
|
+
async def _create_span_call(app, scope, receive, send, **kwargs):
|
|
145
|
+
# type: (Any, Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]], Any) -> None
|
|
146
|
+
integration = sentry_sdk.get_client().get_integration(StarletteIntegration)
|
|
147
|
+
if integration is None or not integration.middleware_spans:
|
|
148
|
+
return await old_call(app, scope, receive, send, **kwargs)
|
|
149
|
+
|
|
150
|
+
middleware_name = app.__class__.__name__
|
|
151
|
+
|
|
152
|
+
# Update transaction name with middleware name
|
|
153
|
+
name, source = _get_transaction_from_middleware(app, scope, integration)
|
|
154
|
+
if name is not None:
|
|
155
|
+
sentry_sdk.get_current_scope().set_transaction_name(
|
|
156
|
+
name,
|
|
157
|
+
source=source,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
with sentry_sdk.start_span(
|
|
161
|
+
op=OP.MIDDLEWARE_STARLETTE,
|
|
162
|
+
name=middleware_name,
|
|
163
|
+
origin=StarletteIntegration.origin,
|
|
164
|
+
) as middleware_span:
|
|
165
|
+
middleware_span.set_tag("starlette.middleware_name", middleware_name)
|
|
166
|
+
|
|
167
|
+
# Creating spans for the "receive" callback
|
|
168
|
+
async def _sentry_receive(*args, **kwargs):
|
|
169
|
+
# type: (*Any, **Any) -> Any
|
|
170
|
+
with sentry_sdk.start_span(
|
|
171
|
+
op=OP.MIDDLEWARE_STARLETTE_RECEIVE,
|
|
172
|
+
name=getattr(receive, "__qualname__", str(receive)),
|
|
173
|
+
origin=StarletteIntegration.origin,
|
|
174
|
+
) as span:
|
|
175
|
+
span.set_tag("starlette.middleware_name", middleware_name)
|
|
176
|
+
return await receive(*args, **kwargs)
|
|
177
|
+
|
|
178
|
+
receive_name = getattr(receive, "__name__", str(receive))
|
|
179
|
+
receive_patched = receive_name == "_sentry_receive"
|
|
180
|
+
new_receive = _sentry_receive if not receive_patched else receive
|
|
181
|
+
|
|
182
|
+
# Creating spans for the "send" callback
|
|
183
|
+
async def _sentry_send(*args, **kwargs):
|
|
184
|
+
# type: (*Any, **Any) -> Any
|
|
185
|
+
with sentry_sdk.start_span(
|
|
186
|
+
op=OP.MIDDLEWARE_STARLETTE_SEND,
|
|
187
|
+
name=getattr(send, "__qualname__", str(send)),
|
|
188
|
+
origin=StarletteIntegration.origin,
|
|
189
|
+
) as span:
|
|
190
|
+
span.set_tag("starlette.middleware_name", middleware_name)
|
|
191
|
+
return await send(*args, **kwargs)
|
|
192
|
+
|
|
193
|
+
send_name = getattr(send, "__name__", str(send))
|
|
194
|
+
send_patched = send_name == "_sentry_send"
|
|
195
|
+
new_send = _sentry_send if not send_patched else send
|
|
196
|
+
|
|
197
|
+
return await old_call(app, scope, new_receive, new_send, **kwargs)
|
|
198
|
+
|
|
199
|
+
not_yet_patched = old_call.__name__ not in [
|
|
200
|
+
"_create_span_call",
|
|
201
|
+
"_sentry_authenticationmiddleware_call",
|
|
202
|
+
"_sentry_exceptionmiddleware_call",
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
if not_yet_patched:
|
|
206
|
+
middleware_class.__call__ = _create_span_call
|
|
207
|
+
|
|
208
|
+
return middleware_class
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@ensure_integration_enabled(StarletteIntegration)
|
|
212
|
+
def _capture_exception(exception, handled=False):
|
|
213
|
+
# type: (BaseException, **Any) -> None
|
|
214
|
+
event, hint = event_from_exception(
|
|
215
|
+
exception,
|
|
216
|
+
client_options=sentry_sdk.get_client().options,
|
|
217
|
+
mechanism={"type": StarletteIntegration.identifier, "handled": handled},
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def patch_exception_middleware(middleware_class):
|
|
224
|
+
# type: (Any) -> None
|
|
225
|
+
"""
|
|
226
|
+
Capture all exceptions in Starlette app and
|
|
227
|
+
also extract user information.
|
|
228
|
+
"""
|
|
229
|
+
old_middleware_init = middleware_class.__init__
|
|
230
|
+
|
|
231
|
+
not_yet_patched = "_sentry_middleware_init" not in str(old_middleware_init)
|
|
232
|
+
|
|
233
|
+
if not_yet_patched:
|
|
234
|
+
|
|
235
|
+
def _sentry_middleware_init(self, *args, **kwargs):
|
|
236
|
+
# type: (Any, Any, Any) -> None
|
|
237
|
+
old_middleware_init(self, *args, **kwargs)
|
|
238
|
+
|
|
239
|
+
# Patch existing exception handlers
|
|
240
|
+
old_handlers = self._exception_handlers.copy()
|
|
241
|
+
|
|
242
|
+
async def _sentry_patched_exception_handler(self, *args, **kwargs):
|
|
243
|
+
# type: (Any, Any, Any) -> None
|
|
244
|
+
integration = sentry_sdk.get_client().get_integration(
|
|
245
|
+
StarletteIntegration
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
exp = args[0]
|
|
249
|
+
|
|
250
|
+
if integration is not None:
|
|
251
|
+
is_http_server_error = (
|
|
252
|
+
hasattr(exp, "status_code")
|
|
253
|
+
and isinstance(exp.status_code, int)
|
|
254
|
+
and exp.status_code in integration.failed_request_status_codes
|
|
255
|
+
)
|
|
256
|
+
if is_http_server_error:
|
|
257
|
+
_capture_exception(exp, handled=True)
|
|
258
|
+
|
|
259
|
+
# Find a matching handler
|
|
260
|
+
old_handler = None
|
|
261
|
+
for cls in type(exp).__mro__:
|
|
262
|
+
if cls in old_handlers:
|
|
263
|
+
old_handler = old_handlers[cls]
|
|
264
|
+
break
|
|
265
|
+
|
|
266
|
+
if old_handler is None:
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
if _is_async_callable(old_handler):
|
|
270
|
+
return await old_handler(self, *args, **kwargs)
|
|
271
|
+
else:
|
|
272
|
+
return old_handler(self, *args, **kwargs)
|
|
273
|
+
|
|
274
|
+
for key in self._exception_handlers.keys():
|
|
275
|
+
self._exception_handlers[key] = _sentry_patched_exception_handler
|
|
276
|
+
|
|
277
|
+
middleware_class.__init__ = _sentry_middleware_init
|
|
278
|
+
|
|
279
|
+
old_call = middleware_class.__call__
|
|
280
|
+
|
|
281
|
+
async def _sentry_exceptionmiddleware_call(self, scope, receive, send):
|
|
282
|
+
# type: (Dict[str, Any], Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]]) -> None
|
|
283
|
+
# Also add the user (that was eventually set by be Authentication middle
|
|
284
|
+
# that was called before this middleware). This is done because the authentication
|
|
285
|
+
# middleware sets the user in the scope and then (in the same function)
|
|
286
|
+
# calls this exception middelware. In case there is no exception (or no handler
|
|
287
|
+
# for the type of exception occuring) then the exception bubbles up and setting the
|
|
288
|
+
# user information into the sentry scope is done in auth middleware and the
|
|
289
|
+
# ASGI middleware will then send everything to Sentry and this is fine.
|
|
290
|
+
# But if there is an exception happening that the exception middleware here
|
|
291
|
+
# has a handler for, it will send the exception directly to Sentry, so we need
|
|
292
|
+
# the user information right now.
|
|
293
|
+
# This is why we do it here.
|
|
294
|
+
_add_user_to_sentry_scope(scope)
|
|
295
|
+
await old_call(self, scope, receive, send)
|
|
296
|
+
|
|
297
|
+
middleware_class.__call__ = _sentry_exceptionmiddleware_call
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@ensure_integration_enabled(StarletteIntegration)
|
|
301
|
+
def _add_user_to_sentry_scope(scope):
|
|
302
|
+
# type: (Dict[str, Any]) -> None
|
|
303
|
+
"""
|
|
304
|
+
Extracts user information from the ASGI scope and
|
|
305
|
+
adds it to Sentry's scope.
|
|
306
|
+
"""
|
|
307
|
+
if "user" not in scope:
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
if not should_send_default_pii():
|
|
311
|
+
return
|
|
312
|
+
|
|
313
|
+
user_info = {} # type: Dict[str, Any]
|
|
314
|
+
starlette_user = scope["user"]
|
|
315
|
+
|
|
316
|
+
username = getattr(starlette_user, "username", None)
|
|
317
|
+
if username:
|
|
318
|
+
user_info.setdefault("username", starlette_user.username)
|
|
319
|
+
|
|
320
|
+
user_id = getattr(starlette_user, "id", None)
|
|
321
|
+
if user_id:
|
|
322
|
+
user_info.setdefault("id", starlette_user.id)
|
|
323
|
+
|
|
324
|
+
email = getattr(starlette_user, "email", None)
|
|
325
|
+
if email:
|
|
326
|
+
user_info.setdefault("email", starlette_user.email)
|
|
327
|
+
|
|
328
|
+
sentry_scope = sentry_sdk.get_isolation_scope()
|
|
329
|
+
sentry_scope.set_user(user_info)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def patch_authentication_middleware(middleware_class):
|
|
333
|
+
# type: (Any) -> None
|
|
334
|
+
"""
|
|
335
|
+
Add user information to Sentry scope.
|
|
336
|
+
"""
|
|
337
|
+
old_call = middleware_class.__call__
|
|
338
|
+
|
|
339
|
+
not_yet_patched = "_sentry_authenticationmiddleware_call" not in str(old_call)
|
|
340
|
+
|
|
341
|
+
if not_yet_patched:
|
|
342
|
+
|
|
343
|
+
async def _sentry_authenticationmiddleware_call(self, scope, receive, send):
|
|
344
|
+
# type: (Dict[str, Any], Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]]) -> None
|
|
345
|
+
await old_call(self, scope, receive, send)
|
|
346
|
+
_add_user_to_sentry_scope(scope)
|
|
347
|
+
|
|
348
|
+
middleware_class.__call__ = _sentry_authenticationmiddleware_call
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def patch_middlewares():
|
|
352
|
+
# type: () -> None
|
|
353
|
+
"""
|
|
354
|
+
Patches Starlettes `Middleware` class to record
|
|
355
|
+
spans for every middleware invoked.
|
|
356
|
+
"""
|
|
357
|
+
old_middleware_init = Middleware.__init__
|
|
358
|
+
|
|
359
|
+
not_yet_patched = "_sentry_middleware_init" not in str(old_middleware_init)
|
|
360
|
+
|
|
361
|
+
if not_yet_patched:
|
|
362
|
+
|
|
363
|
+
def _sentry_middleware_init(self, cls, *args, **kwargs):
|
|
364
|
+
# type: (Any, Any, Any, Any) -> None
|
|
365
|
+
if cls == SentryAsgiMiddleware:
|
|
366
|
+
return old_middleware_init(self, cls, *args, **kwargs)
|
|
367
|
+
|
|
368
|
+
span_enabled_cls = _enable_span_for_middleware(cls)
|
|
369
|
+
old_middleware_init(self, span_enabled_cls, *args, **kwargs)
|
|
370
|
+
|
|
371
|
+
if cls == AuthenticationMiddleware:
|
|
372
|
+
patch_authentication_middleware(cls)
|
|
373
|
+
|
|
374
|
+
if cls == ExceptionMiddleware:
|
|
375
|
+
patch_exception_middleware(cls)
|
|
376
|
+
|
|
377
|
+
Middleware.__init__ = _sentry_middleware_init
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def patch_asgi_app():
|
|
381
|
+
# type: () -> None
|
|
382
|
+
"""
|
|
383
|
+
Instrument Starlette ASGI app using the SentryAsgiMiddleware.
|
|
384
|
+
"""
|
|
385
|
+
old_app = Starlette.__call__
|
|
386
|
+
|
|
387
|
+
async def _sentry_patched_asgi_app(self, scope, receive, send):
|
|
388
|
+
# type: (Starlette, StarletteScope, Receive, Send) -> None
|
|
389
|
+
integration = sentry_sdk.get_client().get_integration(StarletteIntegration)
|
|
390
|
+
if integration is None:
|
|
391
|
+
return await old_app(self, scope, receive, send)
|
|
392
|
+
|
|
393
|
+
middleware = SentryAsgiMiddleware(
|
|
394
|
+
lambda *a, **kw: old_app(self, *a, **kw),
|
|
395
|
+
mechanism_type=StarletteIntegration.identifier,
|
|
396
|
+
transaction_style=integration.transaction_style,
|
|
397
|
+
span_origin=StarletteIntegration.origin,
|
|
398
|
+
http_methods_to_capture=(
|
|
399
|
+
integration.http_methods_to_capture
|
|
400
|
+
if integration
|
|
401
|
+
else DEFAULT_HTTP_METHODS_TO_CAPTURE
|
|
402
|
+
),
|
|
403
|
+
asgi_version=3,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
return await middleware(scope, receive, send)
|
|
407
|
+
|
|
408
|
+
Starlette.__call__ = _sentry_patched_asgi_app
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# This was vendored in from Starlette to support Starlette 0.19.1 because
|
|
412
|
+
# this function was only introduced in 0.20.x
|
|
413
|
+
def _is_async_callable(obj):
|
|
414
|
+
# type: (Any) -> bool
|
|
415
|
+
while isinstance(obj, functools.partial):
|
|
416
|
+
obj = obj.func
|
|
417
|
+
|
|
418
|
+
return asyncio.iscoroutinefunction(obj) or (
|
|
419
|
+
callable(obj) and asyncio.iscoroutinefunction(obj.__call__)
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def patch_request_response():
|
|
424
|
+
# type: () -> None
|
|
425
|
+
old_request_response = starlette.routing.request_response
|
|
426
|
+
|
|
427
|
+
def _sentry_request_response(func):
|
|
428
|
+
# type: (Callable[[Any], Any]) -> ASGIApp
|
|
429
|
+
old_func = func
|
|
430
|
+
|
|
431
|
+
is_coroutine = _is_async_callable(old_func)
|
|
432
|
+
if is_coroutine:
|
|
433
|
+
|
|
434
|
+
async def _sentry_async_func(*args, **kwargs):
|
|
435
|
+
# type: (*Any, **Any) -> Any
|
|
436
|
+
integration = sentry_sdk.get_client().get_integration(
|
|
437
|
+
StarletteIntegration
|
|
438
|
+
)
|
|
439
|
+
if integration is None:
|
|
440
|
+
return await old_func(*args, **kwargs)
|
|
441
|
+
|
|
442
|
+
request = args[0]
|
|
443
|
+
|
|
444
|
+
_set_transaction_name_and_source(
|
|
445
|
+
sentry_sdk.get_current_scope(),
|
|
446
|
+
integration.transaction_style,
|
|
447
|
+
request,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
sentry_scope = sentry_sdk.get_isolation_scope()
|
|
451
|
+
extractor = StarletteRequestExtractor(request)
|
|
452
|
+
info = await extractor.extract_request_info()
|
|
453
|
+
|
|
454
|
+
def _make_request_event_processor(req, integration):
|
|
455
|
+
# type: (Any, Any) -> Callable[[Event, dict[str, Any]], Event]
|
|
456
|
+
def event_processor(event, hint):
|
|
457
|
+
# type: (Event, Dict[str, Any]) -> Event
|
|
458
|
+
|
|
459
|
+
# Add info from request to event
|
|
460
|
+
request_info = event.get("request", {})
|
|
461
|
+
if info:
|
|
462
|
+
if "cookies" in info:
|
|
463
|
+
request_info["cookies"] = info["cookies"]
|
|
464
|
+
if "data" in info:
|
|
465
|
+
request_info["data"] = info["data"]
|
|
466
|
+
event["request"] = deepcopy(request_info)
|
|
467
|
+
|
|
468
|
+
return event
|
|
469
|
+
|
|
470
|
+
return event_processor
|
|
471
|
+
|
|
472
|
+
sentry_scope._name = StarletteIntegration.identifier
|
|
473
|
+
sentry_scope.add_event_processor(
|
|
474
|
+
_make_request_event_processor(request, integration)
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
return await old_func(*args, **kwargs)
|
|
478
|
+
|
|
479
|
+
func = _sentry_async_func
|
|
480
|
+
|
|
481
|
+
else:
|
|
482
|
+
|
|
483
|
+
@functools.wraps(old_func)
|
|
484
|
+
def _sentry_sync_func(*args, **kwargs):
|
|
485
|
+
# type: (*Any, **Any) -> Any
|
|
486
|
+
integration = sentry_sdk.get_client().get_integration(
|
|
487
|
+
StarletteIntegration
|
|
488
|
+
)
|
|
489
|
+
if integration is None:
|
|
490
|
+
return old_func(*args, **kwargs)
|
|
491
|
+
|
|
492
|
+
current_scope = sentry_sdk.get_current_scope()
|
|
493
|
+
if current_scope.transaction is not None:
|
|
494
|
+
current_scope.transaction.update_active_thread()
|
|
495
|
+
|
|
496
|
+
sentry_scope = sentry_sdk.get_isolation_scope()
|
|
497
|
+
if sentry_scope.profile is not None:
|
|
498
|
+
sentry_scope.profile.update_active_thread_id()
|
|
499
|
+
|
|
500
|
+
request = args[0]
|
|
501
|
+
|
|
502
|
+
_set_transaction_name_and_source(
|
|
503
|
+
sentry_scope, integration.transaction_style, request
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
extractor = StarletteRequestExtractor(request)
|
|
507
|
+
cookies = extractor.extract_cookies_from_request()
|
|
508
|
+
|
|
509
|
+
def _make_request_event_processor(req, integration):
|
|
510
|
+
# type: (Any, Any) -> Callable[[Event, dict[str, Any]], Event]
|
|
511
|
+
def event_processor(event, hint):
|
|
512
|
+
# type: (Event, dict[str, Any]) -> Event
|
|
513
|
+
|
|
514
|
+
# Extract information from request
|
|
515
|
+
request_info = event.get("request", {})
|
|
516
|
+
if cookies:
|
|
517
|
+
request_info["cookies"] = cookies
|
|
518
|
+
|
|
519
|
+
event["request"] = deepcopy(request_info)
|
|
520
|
+
|
|
521
|
+
return event
|
|
522
|
+
|
|
523
|
+
return event_processor
|
|
524
|
+
|
|
525
|
+
sentry_scope._name = StarletteIntegration.identifier
|
|
526
|
+
sentry_scope.add_event_processor(
|
|
527
|
+
_make_request_event_processor(request, integration)
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
return old_func(*args, **kwargs)
|
|
531
|
+
|
|
532
|
+
func = _sentry_sync_func
|
|
533
|
+
|
|
534
|
+
return old_request_response(func)
|
|
535
|
+
|
|
536
|
+
starlette.routing.request_response = _sentry_request_response
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def patch_templates():
|
|
540
|
+
# type: () -> None
|
|
541
|
+
|
|
542
|
+
# If markupsafe is not installed, then Jinja2 is not installed
|
|
543
|
+
# (markupsafe is a dependency of Jinja2)
|
|
544
|
+
# In this case we do not need to patch the Jinja2Templates class
|
|
545
|
+
try:
|
|
546
|
+
from markupsafe import Markup
|
|
547
|
+
except ImportError:
|
|
548
|
+
return # Nothing to do
|
|
549
|
+
|
|
550
|
+
from starlette.templating import Jinja2Templates # type: ignore
|
|
551
|
+
|
|
552
|
+
old_jinja2templates_init = Jinja2Templates.__init__
|
|
553
|
+
|
|
554
|
+
not_yet_patched = "_sentry_jinja2templates_init" not in str(
|
|
555
|
+
old_jinja2templates_init
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
if not_yet_patched:
|
|
559
|
+
|
|
560
|
+
def _sentry_jinja2templates_init(self, *args, **kwargs):
|
|
561
|
+
# type: (Jinja2Templates, *Any, **Any) -> None
|
|
562
|
+
def add_sentry_trace_meta(request):
|
|
563
|
+
# type: (Request) -> Dict[str, Any]
|
|
564
|
+
trace_meta = Markup(
|
|
565
|
+
sentry_sdk.get_current_scope().trace_propagation_meta()
|
|
566
|
+
)
|
|
567
|
+
return {
|
|
568
|
+
"sentry_trace_meta": trace_meta,
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
kwargs.setdefault("context_processors", [])
|
|
572
|
+
|
|
573
|
+
if add_sentry_trace_meta not in kwargs["context_processors"]:
|
|
574
|
+
kwargs["context_processors"].append(add_sentry_trace_meta)
|
|
575
|
+
|
|
576
|
+
return old_jinja2templates_init(self, *args, **kwargs)
|
|
577
|
+
|
|
578
|
+
Jinja2Templates.__init__ = _sentry_jinja2templates_init
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
class StarletteRequestExtractor:
|
|
582
|
+
"""
|
|
583
|
+
Extracts useful information from the Starlette request
|
|
584
|
+
(like form data or cookies) and adds it to the Sentry event.
|
|
585
|
+
"""
|
|
586
|
+
|
|
587
|
+
request = None # type: Request
|
|
588
|
+
|
|
589
|
+
def __init__(self, request):
|
|
590
|
+
# type: (StarletteRequestExtractor, Request) -> None
|
|
591
|
+
self.request = request
|
|
592
|
+
|
|
593
|
+
def extract_cookies_from_request(self):
|
|
594
|
+
# type: (StarletteRequestExtractor) -> Optional[Dict[str, Any]]
|
|
595
|
+
cookies = None # type: Optional[Dict[str, Any]]
|
|
596
|
+
if should_send_default_pii():
|
|
597
|
+
cookies = self.cookies()
|
|
598
|
+
|
|
599
|
+
return cookies
|
|
600
|
+
|
|
601
|
+
async def extract_request_info(self):
|
|
602
|
+
# type: (StarletteRequestExtractor) -> Optional[Dict[str, Any]]
|
|
603
|
+
client = sentry_sdk.get_client()
|
|
604
|
+
|
|
605
|
+
request_info = {} # type: Dict[str, Any]
|
|
606
|
+
|
|
607
|
+
with capture_internal_exceptions():
|
|
608
|
+
# Add cookies
|
|
609
|
+
if should_send_default_pii():
|
|
610
|
+
request_info["cookies"] = self.cookies()
|
|
611
|
+
|
|
612
|
+
# If there is no body, just return the cookies
|
|
613
|
+
content_length = await self.content_length()
|
|
614
|
+
if not content_length:
|
|
615
|
+
return request_info
|
|
616
|
+
|
|
617
|
+
# Add annotation if body is too big
|
|
618
|
+
if content_length and not request_body_within_bounds(
|
|
619
|
+
client, content_length
|
|
620
|
+
):
|
|
621
|
+
request_info["data"] = AnnotatedValue.removed_because_over_size_limit()
|
|
622
|
+
return request_info
|
|
623
|
+
|
|
624
|
+
# Add JSON body, if it is a JSON request
|
|
625
|
+
json = await self.json()
|
|
626
|
+
if json:
|
|
627
|
+
request_info["data"] = json
|
|
628
|
+
return request_info
|
|
629
|
+
|
|
630
|
+
# Add form as key/value pairs, if request has form data
|
|
631
|
+
form = await self.form()
|
|
632
|
+
if form:
|
|
633
|
+
form_data = {}
|
|
634
|
+
for key, val in form.items():
|
|
635
|
+
is_file = isinstance(val, UploadFile)
|
|
636
|
+
form_data[key] = (
|
|
637
|
+
val
|
|
638
|
+
if not is_file
|
|
639
|
+
else AnnotatedValue.removed_because_raw_data()
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
request_info["data"] = form_data
|
|
643
|
+
return request_info
|
|
644
|
+
|
|
645
|
+
# Raw data, do not add body just an annotation
|
|
646
|
+
request_info["data"] = AnnotatedValue.removed_because_raw_data()
|
|
647
|
+
return request_info
|
|
648
|
+
|
|
649
|
+
async def content_length(self):
|
|
650
|
+
# type: (StarletteRequestExtractor) -> Optional[int]
|
|
651
|
+
if "content-length" in self.request.headers:
|
|
652
|
+
return int(self.request.headers["content-length"])
|
|
653
|
+
|
|
654
|
+
return None
|
|
655
|
+
|
|
656
|
+
def cookies(self):
|
|
657
|
+
# type: (StarletteRequestExtractor) -> Dict[str, Any]
|
|
658
|
+
return self.request.cookies
|
|
659
|
+
|
|
660
|
+
async def form(self):
|
|
661
|
+
# type: (StarletteRequestExtractor) -> Any
|
|
662
|
+
if multipart is None:
|
|
663
|
+
return None
|
|
664
|
+
|
|
665
|
+
# Parse the body first to get it cached, as Starlette does not cache form() as it
|
|
666
|
+
# does with body() and json() https://github.com/encode/starlette/discussions/1933
|
|
667
|
+
# Calling `.form()` without calling `.body()` first will
|
|
668
|
+
# potentially break the users project.
|
|
669
|
+
await self.request.body()
|
|
670
|
+
|
|
671
|
+
return await self.request.form()
|
|
672
|
+
|
|
673
|
+
def is_json(self):
|
|
674
|
+
# type: (StarletteRequestExtractor) -> bool
|
|
675
|
+
return _is_json_content_type(self.request.headers.get("content-type"))
|
|
676
|
+
|
|
677
|
+
async def json(self):
|
|
678
|
+
# type: (StarletteRequestExtractor) -> Optional[Dict[str, Any]]
|
|
679
|
+
if not self.is_json():
|
|
680
|
+
return None
|
|
681
|
+
try:
|
|
682
|
+
return await self.request.json()
|
|
683
|
+
except JSONDecodeError:
|
|
684
|
+
return None
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def _transaction_name_from_router(scope):
|
|
688
|
+
# type: (StarletteScope) -> Optional[str]
|
|
689
|
+
router = scope.get("router")
|
|
690
|
+
if not router:
|
|
691
|
+
return None
|
|
692
|
+
|
|
693
|
+
for route in router.routes:
|
|
694
|
+
match = route.matches(scope)
|
|
695
|
+
if match[0] == Match.FULL:
|
|
696
|
+
try:
|
|
697
|
+
return route.path
|
|
698
|
+
except AttributeError:
|
|
699
|
+
# routes added via app.host() won't have a path attribute
|
|
700
|
+
return scope.get("path")
|
|
701
|
+
|
|
702
|
+
return None
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _set_transaction_name_and_source(scope, transaction_style, request):
|
|
706
|
+
# type: (sentry_sdk.Scope, str, Any) -> None
|
|
707
|
+
name = None
|
|
708
|
+
source = SOURCE_FOR_STYLE[transaction_style]
|
|
709
|
+
|
|
710
|
+
if transaction_style == "endpoint":
|
|
711
|
+
endpoint = request.scope.get("endpoint")
|
|
712
|
+
if endpoint:
|
|
713
|
+
name = transaction_from_function(endpoint) or None
|
|
714
|
+
|
|
715
|
+
elif transaction_style == "url":
|
|
716
|
+
name = _transaction_name_from_router(request.scope)
|
|
717
|
+
|
|
718
|
+
if name is None:
|
|
719
|
+
name = _DEFAULT_TRANSACTION_NAME
|
|
720
|
+
source = TransactionSource.ROUTE
|
|
721
|
+
|
|
722
|
+
scope.set_transaction_name(name, source=source)
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def _get_transaction_from_middleware(app, asgi_scope, integration):
|
|
726
|
+
# type: (Any, Dict[str, Any], StarletteIntegration) -> Tuple[Optional[str], Optional[str]]
|
|
727
|
+
name = None
|
|
728
|
+
source = None
|
|
729
|
+
|
|
730
|
+
if integration.transaction_style == "endpoint":
|
|
731
|
+
name = transaction_from_function(app.__class__)
|
|
732
|
+
source = TransactionSource.COMPONENT
|
|
733
|
+
elif integration.transaction_style == "url":
|
|
734
|
+
name = _transaction_name_from_router(asgi_scope)
|
|
735
|
+
source = TransactionSource.ROUTE
|
|
736
|
+
|
|
737
|
+
return name, source
|