sentry-sdk 2.30.0__py2.py3-none-any.whl → 3.0.0a2__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.
Potentially problematic release.
This version of sentry-sdk might be problematic. Click here for more details.
- sentry_sdk/__init__.py +3 -8
- sentry_sdk/_compat.py +0 -1
- sentry_sdk/_init_implementation.py +6 -44
- sentry_sdk/_types.py +2 -64
- sentry_sdk/ai/monitoring.py +14 -10
- sentry_sdk/ai/utils.py +1 -1
- sentry_sdk/api.py +56 -169
- sentry_sdk/client.py +27 -72
- sentry_sdk/consts.py +60 -23
- sentry_sdk/debug.py +0 -10
- sentry_sdk/envelope.py +1 -3
- sentry_sdk/feature_flags.py +1 -1
- sentry_sdk/integrations/__init__.py +4 -2
- sentry_sdk/integrations/_asgi_common.py +5 -6
- sentry_sdk/integrations/_wsgi_common.py +11 -40
- sentry_sdk/integrations/aiohttp.py +104 -57
- sentry_sdk/integrations/anthropic.py +10 -7
- sentry_sdk/integrations/arq.py +24 -13
- sentry_sdk/integrations/asgi.py +102 -83
- sentry_sdk/integrations/asyncio.py +1 -0
- sentry_sdk/integrations/asyncpg.py +45 -30
- sentry_sdk/integrations/aws_lambda.py +109 -92
- sentry_sdk/integrations/boto3.py +38 -9
- sentry_sdk/integrations/bottle.py +1 -1
- sentry_sdk/integrations/celery/__init__.py +51 -41
- sentry_sdk/integrations/clickhouse_driver.py +59 -28
- sentry_sdk/integrations/cohere.py +2 -0
- sentry_sdk/integrations/django/__init__.py +25 -46
- sentry_sdk/integrations/django/asgi.py +6 -2
- sentry_sdk/integrations/django/caching.py +13 -22
- sentry_sdk/integrations/django/middleware.py +1 -0
- sentry_sdk/integrations/django/signals_handlers.py +3 -1
- sentry_sdk/integrations/django/templates.py +8 -12
- sentry_sdk/integrations/django/transactions.py +1 -6
- sentry_sdk/integrations/django/views.py +5 -2
- sentry_sdk/integrations/falcon.py +7 -25
- sentry_sdk/integrations/fastapi.py +3 -3
- sentry_sdk/integrations/flask.py +1 -1
- sentry_sdk/integrations/gcp.py +63 -38
- sentry_sdk/integrations/graphene.py +6 -13
- sentry_sdk/integrations/grpc/aio/client.py +14 -8
- sentry_sdk/integrations/grpc/aio/server.py +19 -21
- sentry_sdk/integrations/grpc/client.py +8 -6
- sentry_sdk/integrations/grpc/server.py +12 -14
- sentry_sdk/integrations/httpx.py +47 -12
- sentry_sdk/integrations/huey.py +26 -22
- sentry_sdk/integrations/huggingface_hub.py +1 -0
- sentry_sdk/integrations/langchain.py +22 -15
- sentry_sdk/integrations/litestar.py +4 -2
- sentry_sdk/integrations/logging.py +7 -2
- sentry_sdk/integrations/openai.py +2 -0
- sentry_sdk/integrations/pymongo.py +18 -25
- sentry_sdk/integrations/pyramid.py +1 -1
- sentry_sdk/integrations/quart.py +3 -3
- sentry_sdk/integrations/ray.py +23 -17
- sentry_sdk/integrations/redis/_async_common.py +29 -18
- sentry_sdk/integrations/redis/_sync_common.py +28 -19
- sentry_sdk/integrations/redis/modules/caches.py +13 -10
- sentry_sdk/integrations/redis/modules/queries.py +14 -11
- sentry_sdk/integrations/redis/rb.py +4 -4
- sentry_sdk/integrations/redis/redis.py +6 -6
- sentry_sdk/integrations/redis/redis_cluster.py +18 -18
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +4 -4
- sentry_sdk/integrations/redis/utils.py +64 -24
- sentry_sdk/integrations/rq.py +68 -23
- sentry_sdk/integrations/rust_tracing.py +28 -43
- sentry_sdk/integrations/sanic.py +23 -13
- sentry_sdk/integrations/socket.py +9 -5
- sentry_sdk/integrations/sqlalchemy.py +8 -8
- sentry_sdk/integrations/starlette.py +11 -31
- sentry_sdk/integrations/starlite.py +4 -2
- sentry_sdk/integrations/stdlib.py +56 -9
- sentry_sdk/integrations/strawberry.py +40 -59
- sentry_sdk/integrations/threading.py +10 -26
- sentry_sdk/integrations/tornado.py +57 -18
- sentry_sdk/integrations/trytond.py +4 -1
- sentry_sdk/integrations/wsgi.py +84 -38
- sentry_sdk/opentelemetry/__init__.py +9 -0
- sentry_sdk/opentelemetry/consts.py +33 -0
- sentry_sdk/opentelemetry/contextvars_context.py +81 -0
- sentry_sdk/{integrations/opentelemetry → opentelemetry}/propagator.py +19 -28
- sentry_sdk/opentelemetry/sampler.py +326 -0
- sentry_sdk/opentelemetry/scope.py +218 -0
- sentry_sdk/opentelemetry/span_processor.py +335 -0
- sentry_sdk/opentelemetry/tracing.py +59 -0
- sentry_sdk/opentelemetry/utils.py +484 -0
- sentry_sdk/profiler/__init__.py +0 -40
- sentry_sdk/profiler/continuous_profiler.py +1 -30
- sentry_sdk/profiler/transaction_profiler.py +5 -56
- sentry_sdk/scope.py +108 -361
- sentry_sdk/sessions.py +0 -87
- sentry_sdk/tracing.py +415 -1161
- sentry_sdk/tracing_utils.py +130 -166
- sentry_sdk/transport.py +4 -104
- sentry_sdk/utils.py +169 -152
- {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/METADATA +3 -5
- sentry_sdk-3.0.0a2.dist-info/RECORD +154 -0
- sentry_sdk-3.0.0a2.dist-info/entry_points.txt +2 -0
- sentry_sdk/hub.py +0 -739
- sentry_sdk/integrations/opentelemetry/__init__.py +0 -7
- sentry_sdk/integrations/opentelemetry/consts.py +0 -5
- sentry_sdk/integrations/opentelemetry/integration.py +0 -58
- sentry_sdk/integrations/opentelemetry/span_processor.py +0 -391
- sentry_sdk/metrics.py +0 -965
- sentry_sdk-2.30.0.dist-info/RECORD +0 -152
- sentry_sdk-2.30.0.dist-info/entry_points.txt +0 -2
- {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/WHEEL +0 -0
- {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/top_level.txt +0 -0
|
@@ -3,8 +3,14 @@ import weakref
|
|
|
3
3
|
from functools import wraps
|
|
4
4
|
|
|
5
5
|
import sentry_sdk
|
|
6
|
-
from sentry_sdk.
|
|
7
|
-
|
|
6
|
+
from sentry_sdk.consts import (
|
|
7
|
+
OP,
|
|
8
|
+
SPANSTATUS,
|
|
9
|
+
SPANDATA,
|
|
10
|
+
BAGGAGE_HEADER_NAME,
|
|
11
|
+
SOURCE_FOR_STYLE,
|
|
12
|
+
TransactionSource,
|
|
13
|
+
)
|
|
8
14
|
from sentry_sdk.integrations import (
|
|
9
15
|
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
|
|
10
16
|
_check_minimum_version,
|
|
@@ -15,22 +21,20 @@ from sentry_sdk.integrations.logging import ignore_logger
|
|
|
15
21
|
from sentry_sdk.sessions import track_session
|
|
16
22
|
from sentry_sdk.integrations._wsgi_common import (
|
|
17
23
|
_filter_headers,
|
|
24
|
+
_request_headers_to_span_attributes,
|
|
18
25
|
request_body_within_bounds,
|
|
19
26
|
)
|
|
20
|
-
from sentry_sdk.tracing import (
|
|
21
|
-
BAGGAGE_HEADER_NAME,
|
|
22
|
-
SOURCE_FOR_STYLE,
|
|
23
|
-
TransactionSource,
|
|
24
|
-
)
|
|
25
27
|
from sentry_sdk.tracing_utils import should_propagate_trace
|
|
26
28
|
from sentry_sdk.utils import (
|
|
27
29
|
capture_internal_exceptions,
|
|
28
30
|
ensure_integration_enabled,
|
|
29
31
|
event_from_exception,
|
|
32
|
+
http_client_status_to_breadcrumb_level,
|
|
30
33
|
logger,
|
|
31
34
|
parse_url,
|
|
32
35
|
parse_version,
|
|
33
36
|
reraise,
|
|
37
|
+
set_thread_info_from_span,
|
|
34
38
|
transaction_from_function,
|
|
35
39
|
HAS_REAL_CONTEXTVARS,
|
|
36
40
|
CONTEXTVARS_ERROR_MESSAGE,
|
|
@@ -67,6 +71,13 @@ if TYPE_CHECKING:
|
|
|
67
71
|
|
|
68
72
|
TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern")
|
|
69
73
|
|
|
74
|
+
REQUEST_PROPERTY_TO_ATTRIBUTE = {
|
|
75
|
+
"query_string": "url.query",
|
|
76
|
+
"method": "http.request.method",
|
|
77
|
+
"scheme": "url.scheme",
|
|
78
|
+
"path": "url.path",
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
|
|
71
82
|
class AioHttpIntegration(Integration):
|
|
72
83
|
identifier = "aiohttp"
|
|
@@ -123,51 +134,38 @@ class AioHttpIntegration(Integration):
|
|
|
123
134
|
scope.add_event_processor(_make_request_processor(weak_request))
|
|
124
135
|
|
|
125
136
|
headers = dict(request.headers)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
try:
|
|
160
|
-
# A valid response handler will return a valid response with a status. But, if the handler
|
|
161
|
-
# returns an invalid response (e.g. None), the line below will raise an AttributeError.
|
|
162
|
-
# Even though this is likely invalid, we need to handle this case to ensure we don't break
|
|
163
|
-
# the application.
|
|
164
|
-
response_status = response.status
|
|
165
|
-
except AttributeError:
|
|
166
|
-
pass
|
|
167
|
-
else:
|
|
168
|
-
transaction.set_http_status(response_status)
|
|
169
|
-
|
|
170
|
-
return response
|
|
137
|
+
with sentry_sdk.continue_trace(headers):
|
|
138
|
+
with sentry_sdk.start_span(
|
|
139
|
+
op=OP.HTTP_SERVER,
|
|
140
|
+
# If this transaction name makes it to the UI, AIOHTTP's
|
|
141
|
+
# URL resolver did not find a route or died trying.
|
|
142
|
+
name="generic AIOHTTP request",
|
|
143
|
+
source=TransactionSource.ROUTE,
|
|
144
|
+
origin=AioHttpIntegration.origin,
|
|
145
|
+
attributes=_prepopulate_attributes(request),
|
|
146
|
+
) as span:
|
|
147
|
+
try:
|
|
148
|
+
response = await old_handle(self, request)
|
|
149
|
+
except HTTPException as e:
|
|
150
|
+
span.set_http_status(e.status_code)
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
e.status_code
|
|
154
|
+
in integration._failed_request_status_codes
|
|
155
|
+
):
|
|
156
|
+
_capture_exception()
|
|
157
|
+
|
|
158
|
+
raise
|
|
159
|
+
except (asyncio.CancelledError, ConnectionResetError):
|
|
160
|
+
span.set_status(SPANSTATUS.CANCELLED)
|
|
161
|
+
raise
|
|
162
|
+
except Exception:
|
|
163
|
+
# This will probably map to a 500 but seems like we
|
|
164
|
+
# have no way to tell. Do not set span status.
|
|
165
|
+
reraise(*_capture_exception())
|
|
166
|
+
|
|
167
|
+
span.set_http_status(response.status)
|
|
168
|
+
return response
|
|
171
169
|
|
|
172
170
|
Application._handle = sentry_app_handle
|
|
173
171
|
|
|
@@ -238,12 +236,21 @@ def create_trace_config():
|
|
|
238
236
|
name="%s %s"
|
|
239
237
|
% (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE),
|
|
240
238
|
origin=AioHttpIntegration.origin,
|
|
239
|
+
only_if_parent=True,
|
|
241
240
|
)
|
|
242
|
-
|
|
241
|
+
|
|
242
|
+
data = {
|
|
243
|
+
SPANDATA.HTTP_METHOD: method,
|
|
244
|
+
}
|
|
245
|
+
set_thread_info_from_span(data, span)
|
|
246
|
+
|
|
243
247
|
if parsed_url is not None:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
data["url"] = parsed_url.url
|
|
249
|
+
data[SPANDATA.HTTP_QUERY] = parsed_url.query
|
|
250
|
+
data[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
|
|
251
|
+
|
|
252
|
+
for key, value in data.items():
|
|
253
|
+
span.set_attribute(key, value)
|
|
247
254
|
|
|
248
255
|
client = sentry_sdk.get_client()
|
|
249
256
|
|
|
@@ -268,15 +275,28 @@ def create_trace_config():
|
|
|
268
275
|
params.headers[key] = value
|
|
269
276
|
|
|
270
277
|
trace_config_ctx.span = span
|
|
278
|
+
trace_config_ctx.span_data = data
|
|
271
279
|
|
|
272
280
|
async def on_request_end(session, trace_config_ctx, params):
|
|
273
281
|
# type: (ClientSession, SimpleNamespace, TraceRequestEndParams) -> None
|
|
274
282
|
if trace_config_ctx.span is None:
|
|
275
283
|
return
|
|
276
284
|
|
|
285
|
+
span_data = trace_config_ctx.span_data or {}
|
|
286
|
+
status_code = int(params.response.status)
|
|
287
|
+
span_data[SPANDATA.HTTP_STATUS_CODE] = status_code
|
|
288
|
+
span_data["reason"] = params.response.reason
|
|
289
|
+
|
|
290
|
+
sentry_sdk.add_breadcrumb(
|
|
291
|
+
type="http",
|
|
292
|
+
category="httplib",
|
|
293
|
+
data=span_data,
|
|
294
|
+
level=http_client_status_to_breadcrumb_level(status_code),
|
|
295
|
+
)
|
|
296
|
+
|
|
277
297
|
span = trace_config_ctx.span
|
|
278
298
|
span.set_http_status(int(params.response.status))
|
|
279
|
-
span.
|
|
299
|
+
span.set_attribute("reason", params.response.reason)
|
|
280
300
|
span.finish()
|
|
281
301
|
|
|
282
302
|
trace_config = TraceConfig()
|
|
@@ -355,3 +375,30 @@ def get_aiohttp_request_data(request):
|
|
|
355
375
|
|
|
356
376
|
# request has no body
|
|
357
377
|
return None
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _prepopulate_attributes(request):
|
|
381
|
+
# type: (Request) -> dict[str, Any]
|
|
382
|
+
"""Construct initial span attributes that can be used in traces sampler."""
|
|
383
|
+
attributes = {}
|
|
384
|
+
|
|
385
|
+
for prop, attr in REQUEST_PROPERTY_TO_ATTRIBUTE.items():
|
|
386
|
+
if getattr(request, prop, None) is not None:
|
|
387
|
+
attributes[attr] = getattr(request, prop)
|
|
388
|
+
|
|
389
|
+
if getattr(request, "host", None) is not None:
|
|
390
|
+
try:
|
|
391
|
+
host, port = request.host.split(":")
|
|
392
|
+
attributes["server.address"] = host
|
|
393
|
+
attributes["server.port"] = port
|
|
394
|
+
except ValueError:
|
|
395
|
+
attributes["server.address"] = request.host
|
|
396
|
+
|
|
397
|
+
with capture_internal_exceptions():
|
|
398
|
+
url = f"{request.scheme}://{request.host}{request.path}" # noqa: E231
|
|
399
|
+
if request.query_string:
|
|
400
|
+
attributes["url.full"] = f"{url}?{request.query_string}"
|
|
401
|
+
|
|
402
|
+
attributes.update(_request_headers_to_span_attributes(dict(request.headers)))
|
|
403
|
+
|
|
404
|
+
return attributes
|
|
@@ -121,13 +121,13 @@ def _add_ai_data_to_span(
|
|
|
121
121
|
with capture_internal_exceptions():
|
|
122
122
|
if should_send_default_pii() and integration.include_prompts:
|
|
123
123
|
complete_message = "".join(content_blocks)
|
|
124
|
-
span.
|
|
124
|
+
span.set_attribute(
|
|
125
125
|
SPANDATA.AI_RESPONSES,
|
|
126
126
|
[{"type": "text", "text": complete_message}],
|
|
127
127
|
)
|
|
128
128
|
total_tokens = input_tokens + output_tokens
|
|
129
129
|
record_token_usage(span, input_tokens, output_tokens, total_tokens)
|
|
130
|
-
span.
|
|
130
|
+
span.set_attribute(SPANDATA.AI_STREAMING, True)
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
def _sentry_patched_create_common(f, *args, **kwargs):
|
|
@@ -148,6 +148,7 @@ def _sentry_patched_create_common(f, *args, **kwargs):
|
|
|
148
148
|
op=OP.ANTHROPIC_MESSAGES_CREATE,
|
|
149
149
|
description="Anthropic messages create",
|
|
150
150
|
origin=AnthropicIntegration.origin,
|
|
151
|
+
only_if_parent=True,
|
|
151
152
|
)
|
|
152
153
|
span.__enter__()
|
|
153
154
|
|
|
@@ -158,15 +159,17 @@ def _sentry_patched_create_common(f, *args, **kwargs):
|
|
|
158
159
|
model = kwargs.get("model")
|
|
159
160
|
|
|
160
161
|
with capture_internal_exceptions():
|
|
161
|
-
span.
|
|
162
|
-
span.
|
|
162
|
+
span.set_attribute(SPANDATA.AI_MODEL_ID, model)
|
|
163
|
+
span.set_attribute(SPANDATA.AI_STREAMING, False)
|
|
163
164
|
|
|
164
165
|
if should_send_default_pii() and integration.include_prompts:
|
|
165
|
-
span.
|
|
166
|
+
span.set_attribute(SPANDATA.AI_INPUT_MESSAGES, messages)
|
|
166
167
|
|
|
167
168
|
if hasattr(result, "content"):
|
|
168
169
|
if should_send_default_pii() and integration.include_prompts:
|
|
169
|
-
span.
|
|
170
|
+
span.set_attribute(
|
|
171
|
+
SPANDATA.AI_RESPONSES, _get_responses(result.content)
|
|
172
|
+
)
|
|
170
173
|
_calculate_token_usage(result, span)
|
|
171
174
|
span.__exit__(None, None, None)
|
|
172
175
|
|
|
@@ -214,7 +217,7 @@ def _sentry_patched_create_common(f, *args, **kwargs):
|
|
|
214
217
|
result._iterator = new_iterator()
|
|
215
218
|
|
|
216
219
|
else:
|
|
217
|
-
span.
|
|
220
|
+
span.set_attribute("unknown_response", True)
|
|
218
221
|
span.__exit__(None, None, None)
|
|
219
222
|
|
|
220
223
|
return result
|
sentry_sdk/integrations/arq.py
CHANGED
|
@@ -5,7 +5,7 @@ from sentry_sdk.consts import OP, SPANSTATUS
|
|
|
5
5
|
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
|
|
6
6
|
from sentry_sdk.integrations.logging import ignore_logger
|
|
7
7
|
from sentry_sdk.scope import should_send_default_pii
|
|
8
|
-
from sentry_sdk.tracing import
|
|
8
|
+
from sentry_sdk.tracing import TransactionSource
|
|
9
9
|
from sentry_sdk.utils import (
|
|
10
10
|
capture_internal_exceptions,
|
|
11
11
|
ensure_integration_enabled,
|
|
@@ -37,6 +37,8 @@ if TYPE_CHECKING:
|
|
|
37
37
|
|
|
38
38
|
ARQ_CONTROL_FLOW_EXCEPTIONS = (JobExecutionFailed, Retry, RetryJob)
|
|
39
39
|
|
|
40
|
+
DEFAULT_TRANSACTION_NAME = "unknown arq task"
|
|
41
|
+
|
|
40
42
|
|
|
41
43
|
class ArqIntegration(Integration):
|
|
42
44
|
identifier = "arq"
|
|
@@ -76,7 +78,10 @@ def patch_enqueue_job():
|
|
|
76
78
|
return await old_enqueue_job(self, function, *args, **kwargs)
|
|
77
79
|
|
|
78
80
|
with sentry_sdk.start_span(
|
|
79
|
-
op=OP.QUEUE_SUBMIT_ARQ,
|
|
81
|
+
op=OP.QUEUE_SUBMIT_ARQ,
|
|
82
|
+
name=function,
|
|
83
|
+
origin=ArqIntegration.origin,
|
|
84
|
+
only_if_parent=True,
|
|
80
85
|
):
|
|
81
86
|
return await old_enqueue_job(self, function, *args, **kwargs)
|
|
82
87
|
|
|
@@ -96,18 +101,24 @@ def patch_run_job():
|
|
|
96
101
|
|
|
97
102
|
with sentry_sdk.isolation_scope() as scope:
|
|
98
103
|
scope._name = "arq"
|
|
104
|
+
scope.set_transaction_name(
|
|
105
|
+
DEFAULT_TRANSACTION_NAME,
|
|
106
|
+
source=TransactionSource.TASK,
|
|
107
|
+
)
|
|
99
108
|
scope.clear_breadcrumbs()
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
name="unknown arq task",
|
|
103
|
-
status="ok",
|
|
110
|
+
with sentry_sdk.start_span(
|
|
104
111
|
op=OP.QUEUE_TASK_ARQ,
|
|
112
|
+
name=DEFAULT_TRANSACTION_NAME,
|
|
105
113
|
source=TransactionSource.TASK,
|
|
106
114
|
origin=ArqIntegration.origin,
|
|
107
|
-
)
|
|
115
|
+
) as span:
|
|
116
|
+
return_value = await old_run_job(self, job_id, score)
|
|
117
|
+
|
|
118
|
+
if span.status is None:
|
|
119
|
+
span.set_status(SPANSTATUS.OK)
|
|
108
120
|
|
|
109
|
-
|
|
110
|
-
return await old_run_job(self, job_id, score)
|
|
121
|
+
return return_value
|
|
111
122
|
|
|
112
123
|
Worker.run_job = _sentry_run_job
|
|
113
124
|
|
|
@@ -116,12 +127,12 @@ def _capture_exception(exc_info):
|
|
|
116
127
|
# type: (ExcInfo) -> None
|
|
117
128
|
scope = sentry_sdk.get_current_scope()
|
|
118
129
|
|
|
119
|
-
if scope.
|
|
130
|
+
if scope.root_span is not None:
|
|
120
131
|
if exc_info[0] in ARQ_CONTROL_FLOW_EXCEPTIONS:
|
|
121
|
-
scope.
|
|
132
|
+
scope.root_span.set_status(SPANSTATUS.ABORTED)
|
|
122
133
|
return
|
|
123
134
|
|
|
124
|
-
scope.
|
|
135
|
+
scope.root_span.set_status(SPANSTATUS.INTERNAL_ERROR)
|
|
125
136
|
|
|
126
137
|
event, hint = event_from_exception(
|
|
127
138
|
exc_info,
|
|
@@ -138,8 +149,8 @@ def _make_event_processor(ctx, *args, **kwargs):
|
|
|
138
149
|
|
|
139
150
|
with capture_internal_exceptions():
|
|
140
151
|
scope = sentry_sdk.get_current_scope()
|
|
141
|
-
if scope.
|
|
142
|
-
scope.
|
|
152
|
+
if scope.root_span is not None:
|
|
153
|
+
scope.root_span.name = ctx["job_name"]
|
|
143
154
|
event["transaction"] = ctx["job_name"]
|
|
144
155
|
|
|
145
156
|
tags = event.setdefault("tags", {})
|
sentry_sdk/integrations/asgi.py
CHANGED
|
@@ -10,25 +10,22 @@ from copy import deepcopy
|
|
|
10
10
|
from functools import partial
|
|
11
11
|
|
|
12
12
|
import sentry_sdk
|
|
13
|
-
from sentry_sdk.
|
|
14
|
-
from sentry_sdk.consts import OP
|
|
13
|
+
from sentry_sdk.consts import OP, SOURCE_FOR_STYLE, TransactionSource
|
|
15
14
|
|
|
16
15
|
from sentry_sdk.integrations._asgi_common import (
|
|
17
16
|
_get_headers,
|
|
17
|
+
_get_query,
|
|
18
18
|
_get_request_data,
|
|
19
19
|
_get_url,
|
|
20
20
|
)
|
|
21
21
|
from sentry_sdk.integrations._wsgi_common import (
|
|
22
22
|
DEFAULT_HTTP_METHODS_TO_CAPTURE,
|
|
23
|
-
|
|
23
|
+
_request_headers_to_span_attributes,
|
|
24
24
|
)
|
|
25
25
|
from sentry_sdk.sessions import track_session
|
|
26
|
-
from sentry_sdk.tracing import (
|
|
27
|
-
SOURCE_FOR_STYLE,
|
|
28
|
-
TransactionSource,
|
|
29
|
-
)
|
|
30
26
|
from sentry_sdk.utils import (
|
|
31
27
|
ContextVar,
|
|
28
|
+
capture_internal_exceptions,
|
|
32
29
|
event_from_exception,
|
|
33
30
|
HAS_REAL_CONTEXTVARS,
|
|
34
31
|
CONTEXTVARS_ERROR_MESSAGE,
|
|
@@ -36,7 +33,6 @@ from sentry_sdk.utils import (
|
|
|
36
33
|
transaction_from_function,
|
|
37
34
|
_get_installed_modules,
|
|
38
35
|
)
|
|
39
|
-
from sentry_sdk.tracing import Transaction
|
|
40
36
|
|
|
41
37
|
from typing import TYPE_CHECKING
|
|
42
38
|
|
|
@@ -56,6 +52,14 @@ _DEFAULT_TRANSACTION_NAME = "generic ASGI request"
|
|
|
56
52
|
|
|
57
53
|
TRANSACTION_STYLE_VALUES = ("endpoint", "url")
|
|
58
54
|
|
|
55
|
+
ASGI_SCOPE_PROPERTY_TO_ATTRIBUTE = {
|
|
56
|
+
"http_version": "network.protocol.version",
|
|
57
|
+
"method": "http.request.method",
|
|
58
|
+
"path": "url.path",
|
|
59
|
+
"scheme": "url.scheme",
|
|
60
|
+
"type": "network.protocol.name",
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
|
|
60
64
|
def _capture_exception(exc, mechanism_type="asgi"):
|
|
61
65
|
# type: (Any, str) -> None
|
|
@@ -100,7 +104,7 @@ class SentryAsgiMiddleware:
|
|
|
100
104
|
unsafe_context_data=False, # type: bool
|
|
101
105
|
transaction_style="endpoint", # type: str
|
|
102
106
|
mechanism_type="asgi", # type: str
|
|
103
|
-
span_origin=
|
|
107
|
+
span_origin=None, # type: Optional[str]
|
|
104
108
|
http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: Tuple[str, ...]
|
|
105
109
|
):
|
|
106
110
|
# type: (...) -> None
|
|
@@ -157,24 +161,40 @@ class SentryAsgiMiddleware:
|
|
|
157
161
|
# type: (Any, Any, Any) -> Any
|
|
158
162
|
return await self._run_app(scope, receive, send, asgi_version=3)
|
|
159
163
|
|
|
164
|
+
async def _run_original_app(self, scope, receive, send, asgi_version):
|
|
165
|
+
# type: (Any, Any, Any, Any, int) -> Any
|
|
166
|
+
try:
|
|
167
|
+
if asgi_version == 2:
|
|
168
|
+
return await self.app(scope)(receive, send)
|
|
169
|
+
else:
|
|
170
|
+
return await self.app(scope, receive, send)
|
|
171
|
+
|
|
172
|
+
except Exception as exc:
|
|
173
|
+
_capture_exception(exc, mechanism_type=self.mechanism_type)
|
|
174
|
+
raise exc from None
|
|
175
|
+
|
|
160
176
|
async def _run_app(self, scope, receive, send, asgi_version):
|
|
161
177
|
# type: (Any, Any, Any, Any, int) -> Any
|
|
162
178
|
is_recursive_asgi_middleware = _asgi_middleware_applied.get(False)
|
|
163
179
|
is_lifespan = scope["type"] == "lifespan"
|
|
164
180
|
if is_recursive_asgi_middleware or is_lifespan:
|
|
165
|
-
|
|
166
|
-
if asgi_version == 2:
|
|
167
|
-
return await self.app(scope)(receive, send)
|
|
168
|
-
else:
|
|
169
|
-
return await self.app(scope, receive, send)
|
|
170
|
-
|
|
171
|
-
except Exception as exc:
|
|
172
|
-
_capture_exception(exc, mechanism_type=self.mechanism_type)
|
|
173
|
-
raise exc from None
|
|
181
|
+
return await self._run_original_app(scope, receive, send, asgi_version)
|
|
174
182
|
|
|
175
183
|
_asgi_middleware_applied.set(True)
|
|
176
184
|
try:
|
|
177
185
|
with sentry_sdk.isolation_scope() as sentry_scope:
|
|
186
|
+
(
|
|
187
|
+
transaction_name,
|
|
188
|
+
transaction_source,
|
|
189
|
+
) = self._get_transaction_name_and_source(
|
|
190
|
+
self.transaction_style,
|
|
191
|
+
scope,
|
|
192
|
+
)
|
|
193
|
+
sentry_scope.set_transaction_name(
|
|
194
|
+
transaction_name,
|
|
195
|
+
source=transaction_source,
|
|
196
|
+
)
|
|
197
|
+
|
|
178
198
|
with track_session(sentry_scope, session_mode="request"):
|
|
179
199
|
sentry_scope.clear_breadcrumbs()
|
|
180
200
|
sentry_scope._name = "asgi"
|
|
@@ -182,82 +202,47 @@ class SentryAsgiMiddleware:
|
|
|
182
202
|
sentry_scope.add_event_processor(processor)
|
|
183
203
|
|
|
184
204
|
ty = scope["type"]
|
|
185
|
-
(
|
|
186
|
-
transaction_name,
|
|
187
|
-
transaction_source,
|
|
188
|
-
) = self._get_transaction_name_and_source(
|
|
189
|
-
self.transaction_style,
|
|
190
|
-
scope,
|
|
191
|
-
)
|
|
192
205
|
|
|
193
206
|
method = scope.get("method", "").upper()
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
transaction = Transaction(
|
|
210
|
-
op=OP.HTTP_SERVER,
|
|
207
|
+
should_trace = ty == "websocket" or (
|
|
208
|
+
ty == "http" and method in self.http_methods_to_capture
|
|
209
|
+
)
|
|
210
|
+
if not should_trace:
|
|
211
|
+
return await self._run_original_app(
|
|
212
|
+
scope, receive, send, asgi_version
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
with sentry_sdk.continue_trace(_get_headers(scope)):
|
|
216
|
+
with sentry_sdk.start_span(
|
|
217
|
+
op=(
|
|
218
|
+
OP.WEBSOCKET_SERVER
|
|
219
|
+
if ty == "websocket"
|
|
220
|
+
else OP.HTTP_SERVER
|
|
221
|
+
),
|
|
211
222
|
name=transaction_name,
|
|
212
223
|
source=transaction_source,
|
|
213
224
|
origin=self.span_origin,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if transaction:
|
|
220
|
-
transaction.set_tag("asgi.type", ty)
|
|
221
|
-
logger.debug(
|
|
222
|
-
"[ASGI] Set transaction name and source on transaction: '%s' / '%s'",
|
|
223
|
-
transaction.name,
|
|
224
|
-
transaction.source,
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
with (
|
|
228
|
-
sentry_sdk.start_transaction(
|
|
229
|
-
transaction,
|
|
230
|
-
custom_sampling_context={"asgi_scope": scope},
|
|
231
|
-
)
|
|
232
|
-
if transaction is not None
|
|
233
|
-
else nullcontext()
|
|
234
|
-
):
|
|
235
|
-
logger.debug("[ASGI] Started transaction: %s", transaction)
|
|
236
|
-
try:
|
|
225
|
+
attributes=_prepopulate_attributes(scope),
|
|
226
|
+
) as span:
|
|
227
|
+
if span is not None:
|
|
228
|
+
logger.debug("[ASGI] Started transaction: %s", span)
|
|
229
|
+
span.set_tag("asgi.type", ty)
|
|
237
230
|
|
|
238
231
|
async def _sentry_wrapped_send(event):
|
|
239
232
|
# type: (Dict[str, Any]) -> Any
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
233
|
+
is_http_response = (
|
|
234
|
+
event.get("type") == "http.response.start"
|
|
235
|
+
and span is not None
|
|
236
|
+
and "status" in event
|
|
237
|
+
)
|
|
238
|
+
if is_http_response:
|
|
239
|
+
span.set_http_status(event["status"])
|
|
247
240
|
|
|
248
241
|
return await send(event)
|
|
249
242
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
)
|
|
254
|
-
else:
|
|
255
|
-
return await self.app(
|
|
256
|
-
scope, receive, _sentry_wrapped_send
|
|
257
|
-
)
|
|
258
|
-
except Exception as exc:
|
|
259
|
-
_capture_exception(exc, mechanism_type=self.mechanism_type)
|
|
260
|
-
raise exc from None
|
|
243
|
+
return await self._run_original_app(
|
|
244
|
+
scope, receive, _sentry_wrapped_send, asgi_version
|
|
245
|
+
)
|
|
261
246
|
finally:
|
|
262
247
|
_asgi_middleware_applied.set(False)
|
|
263
248
|
|
|
@@ -336,3 +321,37 @@ class SentryAsgiMiddleware:
|
|
|
336
321
|
return name, source
|
|
337
322
|
|
|
338
323
|
return name, source
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _prepopulate_attributes(scope):
|
|
327
|
+
# type: (Any) -> dict[str, Any]
|
|
328
|
+
"""Unpack ASGI scope into serializable OTel attributes."""
|
|
329
|
+
scope = scope or {}
|
|
330
|
+
|
|
331
|
+
attributes = {}
|
|
332
|
+
for attr, key in ASGI_SCOPE_PROPERTY_TO_ATTRIBUTE.items():
|
|
333
|
+
if scope.get(attr):
|
|
334
|
+
attributes[key] = scope[attr]
|
|
335
|
+
|
|
336
|
+
for attr in ("client", "server"):
|
|
337
|
+
if scope.get(attr):
|
|
338
|
+
try:
|
|
339
|
+
host, port = scope[attr]
|
|
340
|
+
attributes[f"{attr}.address"] = host
|
|
341
|
+
if port is not None:
|
|
342
|
+
attributes[f"{attr}.port"] = port
|
|
343
|
+
except Exception:
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
with capture_internal_exceptions():
|
|
347
|
+
full_url = _get_url(scope)
|
|
348
|
+
query = _get_query(scope)
|
|
349
|
+
if query:
|
|
350
|
+
attributes["url.query"] = query
|
|
351
|
+
full_url = f"{full_url}?{query}"
|
|
352
|
+
|
|
353
|
+
attributes["url.full"] = full_url
|
|
354
|
+
|
|
355
|
+
attributes.update(_request_headers_to_span_attributes(_get_headers(scope)))
|
|
356
|
+
|
|
357
|
+
return attributes
|