sentry-sdk 3.0.0a1__py2.py3-none-any.whl → 3.0.0a3__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 +2 -0
- sentry_sdk/_compat.py +5 -12
- sentry_sdk/_init_implementation.py +7 -7
- sentry_sdk/_log_batcher.py +17 -29
- sentry_sdk/_lru_cache.py +7 -9
- sentry_sdk/_queue.py +2 -4
- sentry_sdk/_types.py +11 -18
- sentry_sdk/_werkzeug.py +5 -7
- sentry_sdk/ai/monitoring.py +44 -31
- sentry_sdk/ai/utils.py +3 -4
- sentry_sdk/api.py +75 -87
- sentry_sdk/attachments.py +10 -12
- sentry_sdk/client.py +137 -155
- sentry_sdk/consts.py +430 -174
- sentry_sdk/crons/api.py +16 -17
- sentry_sdk/crons/decorator.py +25 -27
- sentry_sdk/debug.py +4 -6
- sentry_sdk/envelope.py +46 -112
- sentry_sdk/feature_flags.py +9 -15
- sentry_sdk/integrations/__init__.py +24 -19
- sentry_sdk/integrations/_asgi_common.py +15 -18
- sentry_sdk/integrations/_wsgi_common.py +22 -33
- sentry_sdk/integrations/aiohttp.py +32 -30
- sentry_sdk/integrations/anthropic.py +42 -37
- sentry_sdk/integrations/argv.py +3 -4
- sentry_sdk/integrations/ariadne.py +16 -18
- sentry_sdk/integrations/arq.py +21 -29
- sentry_sdk/integrations/asgi.py +63 -37
- sentry_sdk/integrations/asyncio.py +14 -16
- sentry_sdk/integrations/atexit.py +6 -10
- sentry_sdk/integrations/aws_lambda.py +26 -36
- sentry_sdk/integrations/beam.py +10 -18
- sentry_sdk/integrations/boto3.py +18 -16
- sentry_sdk/integrations/bottle.py +25 -34
- sentry_sdk/integrations/celery/__init__.py +41 -61
- sentry_sdk/integrations/celery/beat.py +23 -27
- sentry_sdk/integrations/celery/utils.py +15 -17
- sentry_sdk/integrations/chalice.py +8 -10
- sentry_sdk/integrations/clickhouse_driver.py +21 -31
- sentry_sdk/integrations/cloud_resource_context.py +9 -16
- sentry_sdk/integrations/cohere.py +27 -33
- sentry_sdk/integrations/dedupe.py +5 -8
- sentry_sdk/integrations/django/__init__.py +57 -72
- sentry_sdk/integrations/django/asgi.py +26 -34
- sentry_sdk/integrations/django/caching.py +23 -19
- sentry_sdk/integrations/django/middleware.py +17 -20
- sentry_sdk/integrations/django/signals_handlers.py +11 -10
- sentry_sdk/integrations/django/templates.py +19 -16
- sentry_sdk/integrations/django/transactions.py +16 -11
- sentry_sdk/integrations/django/views.py +6 -10
- sentry_sdk/integrations/dramatiq.py +21 -21
- sentry_sdk/integrations/excepthook.py +10 -10
- sentry_sdk/integrations/executing.py +3 -4
- sentry_sdk/integrations/falcon.py +27 -42
- sentry_sdk/integrations/fastapi.py +13 -16
- sentry_sdk/integrations/flask.py +31 -38
- sentry_sdk/integrations/gcp.py +13 -16
- sentry_sdk/integrations/gnu_backtrace.py +4 -6
- sentry_sdk/integrations/gql.py +16 -17
- sentry_sdk/integrations/graphene.py +13 -12
- sentry_sdk/integrations/grpc/__init__.py +19 -1
- sentry_sdk/integrations/grpc/aio/server.py +15 -14
- sentry_sdk/integrations/grpc/client.py +19 -9
- sentry_sdk/integrations/grpc/consts.py +2 -0
- sentry_sdk/integrations/grpc/server.py +12 -8
- sentry_sdk/integrations/httpx.py +9 -12
- sentry_sdk/integrations/huey.py +13 -20
- sentry_sdk/integrations/huggingface_hub.py +18 -18
- sentry_sdk/integrations/langchain.py +203 -113
- sentry_sdk/integrations/launchdarkly.py +13 -10
- sentry_sdk/integrations/litestar.py +37 -35
- sentry_sdk/integrations/logging.py +52 -65
- sentry_sdk/integrations/loguru.py +127 -57
- sentry_sdk/integrations/modules.py +3 -4
- sentry_sdk/integrations/openai.py +100 -88
- sentry_sdk/integrations/openai_agents/__init__.py +49 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
- sentry_sdk/integrations/openai_agents/utils.py +201 -0
- sentry_sdk/integrations/openfeature.py +11 -6
- sentry_sdk/integrations/pure_eval.py +6 -10
- sentry_sdk/integrations/pymongo.py +13 -17
- sentry_sdk/integrations/pyramid.py +31 -36
- sentry_sdk/integrations/quart.py +23 -28
- sentry_sdk/integrations/ray.py +73 -64
- sentry_sdk/integrations/redis/__init__.py +7 -4
- sentry_sdk/integrations/redis/_async_common.py +25 -12
- sentry_sdk/integrations/redis/_sync_common.py +19 -13
- sentry_sdk/integrations/redis/modules/caches.py +17 -8
- sentry_sdk/integrations/redis/modules/queries.py +9 -8
- sentry_sdk/integrations/redis/rb.py +3 -2
- sentry_sdk/integrations/redis/redis.py +4 -4
- sentry_sdk/integrations/redis/redis_cluster.py +21 -13
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
- sentry_sdk/integrations/redis/utils.py +23 -24
- sentry_sdk/integrations/rq.py +13 -16
- sentry_sdk/integrations/rust_tracing.py +9 -6
- sentry_sdk/integrations/sanic.py +34 -46
- sentry_sdk/integrations/serverless.py +22 -27
- sentry_sdk/integrations/socket.py +27 -15
- sentry_sdk/integrations/spark/__init__.py +1 -0
- sentry_sdk/integrations/spark/spark_driver.py +45 -83
- sentry_sdk/integrations/spark/spark_worker.py +7 -11
- sentry_sdk/integrations/sqlalchemy.py +22 -19
- sentry_sdk/integrations/starlette.py +86 -90
- sentry_sdk/integrations/starlite.py +28 -34
- sentry_sdk/integrations/statsig.py +5 -4
- sentry_sdk/integrations/stdlib.py +28 -24
- sentry_sdk/integrations/strawberry.py +62 -49
- sentry_sdk/integrations/sys_exit.py +7 -11
- sentry_sdk/integrations/threading.py +12 -14
- sentry_sdk/integrations/tornado.py +28 -32
- sentry_sdk/integrations/trytond.py +4 -3
- sentry_sdk/integrations/typer.py +8 -6
- sentry_sdk/integrations/unleash.py +5 -4
- sentry_sdk/integrations/wsgi.py +47 -46
- sentry_sdk/logger.py +41 -10
- sentry_sdk/monitor.py +16 -28
- sentry_sdk/opentelemetry/consts.py +11 -4
- sentry_sdk/opentelemetry/contextvars_context.py +26 -16
- sentry_sdk/opentelemetry/propagator.py +38 -21
- sentry_sdk/opentelemetry/sampler.py +51 -34
- sentry_sdk/opentelemetry/scope.py +36 -37
- sentry_sdk/opentelemetry/span_processor.py +48 -58
- sentry_sdk/opentelemetry/tracing.py +58 -14
- sentry_sdk/opentelemetry/utils.py +186 -194
- sentry_sdk/profiler/continuous_profiler.py +108 -97
- sentry_sdk/profiler/transaction_profiler.py +70 -97
- sentry_sdk/profiler/utils.py +11 -15
- sentry_sdk/scope.py +251 -273
- sentry_sdk/scrubber.py +22 -26
- sentry_sdk/serializer.py +40 -54
- sentry_sdk/session.py +44 -61
- sentry_sdk/sessions.py +35 -49
- sentry_sdk/spotlight.py +15 -21
- sentry_sdk/tracing.py +121 -187
- sentry_sdk/tracing_utils.py +104 -122
- sentry_sdk/transport.py +131 -157
- sentry_sdk/utils.py +232 -309
- sentry_sdk/worker.py +16 -28
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
- sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
- sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from decimal import Decimal
|
|
2
|
-
from typing import cast
|
|
3
3
|
|
|
4
4
|
from opentelemetry import trace
|
|
5
5
|
from opentelemetry.sdk.trace.sampling import Sampler, SamplingResult, Decision
|
|
@@ -21,15 +21,16 @@ from sentry_sdk.utils import is_valid_sample_rate, logger
|
|
|
21
21
|
from typing import TYPE_CHECKING
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
|
-
from typing import Any, Optional, Sequence
|
|
24
|
+
from typing import Any, Optional, Sequence
|
|
25
25
|
from opentelemetry.context import Context
|
|
26
26
|
from opentelemetry.trace import Link, SpanKind
|
|
27
27
|
from opentelemetry.trace.span import SpanContext
|
|
28
28
|
from opentelemetry.util.types import Attributes
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def get_parent_sampled(
|
|
32
|
-
|
|
31
|
+
def get_parent_sampled(
|
|
32
|
+
parent_context: Optional[SpanContext], trace_id: int
|
|
33
|
+
) -> Optional[bool]:
|
|
33
34
|
if parent_context is None:
|
|
34
35
|
return None
|
|
35
36
|
|
|
@@ -54,8 +55,9 @@ def get_parent_sampled(parent_context, trace_id):
|
|
|
54
55
|
return None
|
|
55
56
|
|
|
56
57
|
|
|
57
|
-
def get_parent_sample_rate(
|
|
58
|
-
|
|
58
|
+
def get_parent_sample_rate(
|
|
59
|
+
parent_context: Optional[SpanContext], trace_id: int
|
|
60
|
+
) -> Optional[float]:
|
|
59
61
|
if parent_context is None:
|
|
60
62
|
return None
|
|
61
63
|
|
|
@@ -74,8 +76,9 @@ def get_parent_sample_rate(parent_context, trace_id):
|
|
|
74
76
|
return None
|
|
75
77
|
|
|
76
78
|
|
|
77
|
-
def get_parent_sample_rand(
|
|
78
|
-
|
|
79
|
+
def get_parent_sample_rand(
|
|
80
|
+
parent_context: Optional[SpanContext], trace_id: int
|
|
81
|
+
) -> Optional[Decimal]:
|
|
79
82
|
if parent_context is None:
|
|
80
83
|
return None
|
|
81
84
|
|
|
@@ -91,8 +94,12 @@ def get_parent_sample_rand(parent_context, trace_id):
|
|
|
91
94
|
return None
|
|
92
95
|
|
|
93
96
|
|
|
94
|
-
def dropped_result(
|
|
95
|
-
|
|
97
|
+
def dropped_result(
|
|
98
|
+
span_context: SpanContext,
|
|
99
|
+
attributes: Attributes,
|
|
100
|
+
sample_rate: Optional[float] = None,
|
|
101
|
+
sample_rand: Optional[Decimal] = None,
|
|
102
|
+
) -> SamplingResult:
|
|
96
103
|
"""
|
|
97
104
|
React to a span getting unsampled and return a DROP SamplingResult.
|
|
98
105
|
|
|
@@ -129,8 +136,12 @@ def dropped_result(span_context, attributes, sample_rate=None, sample_rand=None)
|
|
|
129
136
|
)
|
|
130
137
|
|
|
131
138
|
|
|
132
|
-
def sampled_result(
|
|
133
|
-
|
|
139
|
+
def sampled_result(
|
|
140
|
+
span_context: SpanContext,
|
|
141
|
+
attributes: Attributes,
|
|
142
|
+
sample_rate: Optional[float] = None,
|
|
143
|
+
sample_rand: Optional[Decimal] = None,
|
|
144
|
+
) -> SamplingResult:
|
|
134
145
|
"""
|
|
135
146
|
React to a span being sampled and return a sampled SamplingResult.
|
|
136
147
|
|
|
@@ -151,8 +162,12 @@ def sampled_result(span_context, attributes, sample_rate=None, sample_rand=None)
|
|
|
151
162
|
)
|
|
152
163
|
|
|
153
164
|
|
|
154
|
-
def _update_trace_state(
|
|
155
|
-
|
|
165
|
+
def _update_trace_state(
|
|
166
|
+
span_context: SpanContext,
|
|
167
|
+
sampled: bool,
|
|
168
|
+
sample_rate: Optional[float] = None,
|
|
169
|
+
sample_rand: Optional[Decimal] = None,
|
|
170
|
+
) -> TraceState:
|
|
156
171
|
trace_state = span_context.trace_state
|
|
157
172
|
|
|
158
173
|
sampled = "true" if sampled else "false"
|
|
@@ -175,15 +190,14 @@ def _update_trace_state(span_context, sampled, sample_rate=None, sample_rand=Non
|
|
|
175
190
|
class SentrySampler(Sampler):
|
|
176
191
|
def should_sample(
|
|
177
192
|
self,
|
|
178
|
-
parent_context
|
|
179
|
-
trace_id
|
|
180
|
-
name
|
|
181
|
-
kind
|
|
182
|
-
attributes=None,
|
|
183
|
-
links
|
|
184
|
-
trace_state
|
|
185
|
-
):
|
|
186
|
-
# type: (...) -> SamplingResult
|
|
193
|
+
parent_context: Optional[Context],
|
|
194
|
+
trace_id: int,
|
|
195
|
+
name: str,
|
|
196
|
+
kind: Optional[SpanKind] = None,
|
|
197
|
+
attributes: Attributes = None,
|
|
198
|
+
links: Optional[Sequence[Link]] = None,
|
|
199
|
+
trace_state: Optional[TraceState] = None,
|
|
200
|
+
) -> SamplingResult:
|
|
187
201
|
client = sentry_sdk.get_client()
|
|
188
202
|
|
|
189
203
|
parent_span_context = trace.get_current_span(parent_context).get_span_context()
|
|
@@ -209,13 +223,12 @@ class SentrySampler(Sampler):
|
|
|
209
223
|
sample_rand = parent_sample_rand
|
|
210
224
|
else:
|
|
211
225
|
# We are the head SDK and we need to generate a new sample_rand
|
|
212
|
-
sample_rand =
|
|
226
|
+
sample_rand = _generate_sample_rand(str(trace_id), (0, 1))
|
|
213
227
|
|
|
214
228
|
# Explicit sampled value provided at start_span
|
|
215
|
-
custom_sampled =
|
|
216
|
-
|
|
217
|
-
)
|
|
218
|
-
if custom_sampled is not None:
|
|
229
|
+
custom_sampled = attributes.get(SentrySpanAttribute.CUSTOM_SAMPLED)
|
|
230
|
+
|
|
231
|
+
if custom_sampled is not None and isinstance(custom_sampled, bool):
|
|
219
232
|
if is_root_span:
|
|
220
233
|
sample_rate = float(custom_sampled)
|
|
221
234
|
if sample_rate > 0:
|
|
@@ -262,7 +275,8 @@ class SentrySampler(Sampler):
|
|
|
262
275
|
sample_rate_to_propagate = sample_rate
|
|
263
276
|
|
|
264
277
|
# If the sample rate is invalid, drop the span
|
|
265
|
-
|
|
278
|
+
sample_rate = is_valid_sample_rate(sample_rate, source=self.__class__.__name__)
|
|
279
|
+
if sample_rate is None:
|
|
266
280
|
logger.warning(
|
|
267
281
|
f"[Tracing.Sampler] Discarding {name} because of invalid sample rate."
|
|
268
282
|
)
|
|
@@ -275,7 +289,6 @@ class SentrySampler(Sampler):
|
|
|
275
289
|
sample_rate_to_propagate = sample_rate
|
|
276
290
|
|
|
277
291
|
# Compare sample_rand to sample_rate to make the final sampling decision
|
|
278
|
-
sample_rate = float(cast("Union[bool, float, int]", sample_rate))
|
|
279
292
|
sampled = sample_rand < Decimal.from_float(sample_rate)
|
|
280
293
|
|
|
281
294
|
if sampled:
|
|
@@ -307,9 +320,13 @@ class SentrySampler(Sampler):
|
|
|
307
320
|
return self.__class__.__name__
|
|
308
321
|
|
|
309
322
|
|
|
310
|
-
def create_sampling_context(
|
|
311
|
-
|
|
312
|
-
|
|
323
|
+
def create_sampling_context(
|
|
324
|
+
name: str,
|
|
325
|
+
attributes: Attributes,
|
|
326
|
+
parent_span_context: Optional[SpanContext],
|
|
327
|
+
trace_id: int,
|
|
328
|
+
) -> dict[str, Any]:
|
|
329
|
+
sampling_context: dict[str, Any] = {
|
|
313
330
|
"transaction_context": {
|
|
314
331
|
"name": name,
|
|
315
332
|
"op": attributes.get(SentrySpanAttribute.OP) if attributes else None,
|
|
@@ -318,7 +335,7 @@ def create_sampling_context(name, attributes, parent_span_context, trace_id):
|
|
|
318
335
|
),
|
|
319
336
|
},
|
|
320
337
|
"parent_sampled": get_parent_sampled(parent_span_context, trace_id),
|
|
321
|
-
}
|
|
338
|
+
}
|
|
322
339
|
|
|
323
340
|
if attributes is not None:
|
|
324
341
|
sampling_context.update(attributes)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
from contextlib import contextmanager
|
|
3
3
|
import warnings
|
|
4
4
|
|
|
@@ -24,9 +24,6 @@ from sentry_sdk.opentelemetry.consts import (
|
|
|
24
24
|
SENTRY_USE_ISOLATION_SCOPE_KEY,
|
|
25
25
|
TRACESTATE_SAMPLED_KEY,
|
|
26
26
|
)
|
|
27
|
-
from sentry_sdk.opentelemetry.contextvars_context import (
|
|
28
|
-
SentryContextVarsRuntimeContext,
|
|
29
|
-
)
|
|
30
27
|
from sentry_sdk.opentelemetry.utils import trace_state_from_baggage
|
|
31
28
|
from sentry_sdk.scope import Scope, ScopeType
|
|
32
29
|
from sentry_sdk.tracing import Span
|
|
@@ -38,26 +35,21 @@ if TYPE_CHECKING:
|
|
|
38
35
|
|
|
39
36
|
class PotelScope(Scope):
|
|
40
37
|
@classmethod
|
|
41
|
-
def _get_scopes(cls):
|
|
42
|
-
# type: () -> Optional[Tuple[PotelScope, PotelScope]]
|
|
38
|
+
def _get_scopes(cls) -> Optional[Tuple[PotelScope, PotelScope]]:
|
|
43
39
|
"""
|
|
44
40
|
Returns the current scopes tuple on the otel context. Internal use only.
|
|
45
41
|
"""
|
|
46
|
-
return
|
|
47
|
-
"Optional[Tuple[PotelScope, PotelScope]]", get_value(SENTRY_SCOPES_KEY)
|
|
48
|
-
)
|
|
42
|
+
return validate_scopes(get_value(SENTRY_SCOPES_KEY))
|
|
49
43
|
|
|
50
44
|
@classmethod
|
|
51
|
-
def get_current_scope(cls):
|
|
52
|
-
# type: () -> PotelScope
|
|
45
|
+
def get_current_scope(cls) -> PotelScope:
|
|
53
46
|
"""
|
|
54
47
|
Returns the current scope.
|
|
55
48
|
"""
|
|
56
49
|
return cls._get_current_scope() or _INITIAL_CURRENT_SCOPE
|
|
57
50
|
|
|
58
51
|
@classmethod
|
|
59
|
-
def _get_current_scope(cls):
|
|
60
|
-
# type: () -> Optional[PotelScope]
|
|
52
|
+
def _get_current_scope(cls) -> Optional[PotelScope]:
|
|
61
53
|
"""
|
|
62
54
|
Returns the current scope without creating a new one. Internal use only.
|
|
63
55
|
"""
|
|
@@ -65,16 +57,14 @@ class PotelScope(Scope):
|
|
|
65
57
|
return scopes[0] if scopes else None
|
|
66
58
|
|
|
67
59
|
@classmethod
|
|
68
|
-
def get_isolation_scope(cls):
|
|
69
|
-
# type: () -> PotelScope
|
|
60
|
+
def get_isolation_scope(cls) -> PotelScope:
|
|
70
61
|
"""
|
|
71
62
|
Returns the isolation scope.
|
|
72
63
|
"""
|
|
73
64
|
return cls._get_isolation_scope() or _INITIAL_ISOLATION_SCOPE
|
|
74
65
|
|
|
75
66
|
@classmethod
|
|
76
|
-
def _get_isolation_scope(cls):
|
|
77
|
-
# type: () -> Optional[PotelScope]
|
|
67
|
+
def _get_isolation_scope(cls) -> Optional[PotelScope]:
|
|
78
68
|
"""
|
|
79
69
|
Returns the isolation scope without creating a new one. Internal use only.
|
|
80
70
|
"""
|
|
@@ -82,8 +72,9 @@ class PotelScope(Scope):
|
|
|
82
72
|
return scopes[1] if scopes else None
|
|
83
73
|
|
|
84
74
|
@contextmanager
|
|
85
|
-
def continue_trace(
|
|
86
|
-
|
|
75
|
+
def continue_trace(
|
|
76
|
+
self, environ_or_headers: Dict[str, Any]
|
|
77
|
+
) -> Generator[None, None, None]:
|
|
87
78
|
"""
|
|
88
79
|
Sets the propagation context from environment or headers to continue an incoming trace.
|
|
89
80
|
Any span started within this context manager will use the same trace_id, parent_span_id
|
|
@@ -98,8 +89,7 @@ class PotelScope(Scope):
|
|
|
98
89
|
with use_span(NonRecordingSpan(span_context)):
|
|
99
90
|
yield
|
|
100
91
|
|
|
101
|
-
def _incoming_otel_span_context(self):
|
|
102
|
-
# type: () -> Optional[SpanContext]
|
|
92
|
+
def _incoming_otel_span_context(self) -> Optional[SpanContext]:
|
|
103
93
|
if self._propagation_context is None:
|
|
104
94
|
return None
|
|
105
95
|
# If sentry-trace extraction didn't have a parent_span_id, we don't have an upstream header
|
|
@@ -132,8 +122,7 @@ class PotelScope(Scope):
|
|
|
132
122
|
|
|
133
123
|
return span_context
|
|
134
124
|
|
|
135
|
-
def start_transaction(self, **kwargs):
|
|
136
|
-
# type: (Any) -> Span
|
|
125
|
+
def start_transaction(self, **kwargs: Any) -> Span:
|
|
137
126
|
"""
|
|
138
127
|
.. deprecated:: 3.0.0
|
|
139
128
|
This function is deprecated and will be removed in a future release.
|
|
@@ -146,8 +135,7 @@ class PotelScope(Scope):
|
|
|
146
135
|
)
|
|
147
136
|
return self.start_span(**kwargs)
|
|
148
137
|
|
|
149
|
-
def start_span(self, **kwargs):
|
|
150
|
-
# type: (Any) -> Span
|
|
138
|
+
def start_span(self, **kwargs: Any) -> Span:
|
|
151
139
|
return Span(**kwargs)
|
|
152
140
|
|
|
153
141
|
|
|
@@ -155,8 +143,7 @@ _INITIAL_CURRENT_SCOPE = PotelScope(ty=ScopeType.CURRENT)
|
|
|
155
143
|
_INITIAL_ISOLATION_SCOPE = PotelScope(ty=ScopeType.ISOLATION)
|
|
156
144
|
|
|
157
145
|
|
|
158
|
-
def setup_initial_scopes():
|
|
159
|
-
# type: () -> None
|
|
146
|
+
def setup_initial_scopes() -> None:
|
|
160
147
|
global _INITIAL_CURRENT_SCOPE, _INITIAL_ISOLATION_SCOPE
|
|
161
148
|
_INITIAL_CURRENT_SCOPE = PotelScope(ty=ScopeType.CURRENT)
|
|
162
149
|
_INITIAL_ISOLATION_SCOPE = PotelScope(ty=ScopeType.ISOLATION)
|
|
@@ -165,17 +152,18 @@ def setup_initial_scopes():
|
|
|
165
152
|
attach(set_value(SENTRY_SCOPES_KEY, scopes))
|
|
166
153
|
|
|
167
154
|
|
|
168
|
-
def setup_scope_context_management():
|
|
169
|
-
# type: () -> None
|
|
155
|
+
def setup_scope_context_management() -> None:
|
|
170
156
|
import opentelemetry.context
|
|
157
|
+
from sentry_sdk.opentelemetry.contextvars_context import (
|
|
158
|
+
SentryContextVarsRuntimeContext,
|
|
159
|
+
)
|
|
171
160
|
|
|
172
161
|
opentelemetry.context._RUNTIME_CONTEXT = SentryContextVarsRuntimeContext()
|
|
173
162
|
setup_initial_scopes()
|
|
174
163
|
|
|
175
164
|
|
|
176
165
|
@contextmanager
|
|
177
|
-
def isolation_scope():
|
|
178
|
-
# type: () -> Generator[PotelScope, None, None]
|
|
166
|
+
def isolation_scope() -> Generator[PotelScope, None, None]:
|
|
179
167
|
context = set_value(SENTRY_FORK_ISOLATION_SCOPE_KEY, True)
|
|
180
168
|
token = attach(context)
|
|
181
169
|
try:
|
|
@@ -185,8 +173,7 @@ def isolation_scope():
|
|
|
185
173
|
|
|
186
174
|
|
|
187
175
|
@contextmanager
|
|
188
|
-
def new_scope():
|
|
189
|
-
# type: () -> Generator[PotelScope, None, None]
|
|
176
|
+
def new_scope() -> Generator[PotelScope, None, None]:
|
|
190
177
|
token = attach(get_current())
|
|
191
178
|
try:
|
|
192
179
|
yield PotelScope.get_current_scope()
|
|
@@ -195,8 +182,7 @@ def new_scope():
|
|
|
195
182
|
|
|
196
183
|
|
|
197
184
|
@contextmanager
|
|
198
|
-
def use_scope(scope):
|
|
199
|
-
# type: (PotelScope) -> Generator[PotelScope, None, None]
|
|
185
|
+
def use_scope(scope: PotelScope) -> Generator[PotelScope, None, None]:
|
|
200
186
|
context = set_value(SENTRY_USE_CURRENT_SCOPE_KEY, scope)
|
|
201
187
|
token = attach(context)
|
|
202
188
|
|
|
@@ -207,8 +193,9 @@ def use_scope(scope):
|
|
|
207
193
|
|
|
208
194
|
|
|
209
195
|
@contextmanager
|
|
210
|
-
def use_isolation_scope(
|
|
211
|
-
|
|
196
|
+
def use_isolation_scope(
|
|
197
|
+
isolation_scope: PotelScope,
|
|
198
|
+
) -> Generator[PotelScope, None, None]:
|
|
212
199
|
context = set_value(SENTRY_USE_ISOLATION_SCOPE_KEY, isolation_scope)
|
|
213
200
|
token = attach(context)
|
|
214
201
|
|
|
@@ -216,3 +203,15 @@ def use_isolation_scope(isolation_scope):
|
|
|
216
203
|
yield isolation_scope
|
|
217
204
|
finally:
|
|
218
205
|
detach(token)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def validate_scopes(scopes: Any) -> Optional[Tuple[PotelScope, PotelScope]]:
|
|
209
|
+
if (
|
|
210
|
+
isinstance(scopes, tuple)
|
|
211
|
+
and len(scopes) == 2
|
|
212
|
+
and isinstance(scopes[0], PotelScope)
|
|
213
|
+
and isinstance(scopes[1], PotelScope)
|
|
214
|
+
):
|
|
215
|
+
return scopes
|
|
216
|
+
else:
|
|
217
|
+
return None
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
from collections import deque, defaultdict
|
|
2
|
-
from typing import cast
|
|
3
3
|
|
|
4
4
|
from opentelemetry.trace import (
|
|
5
5
|
format_trace_id,
|
|
@@ -29,6 +29,7 @@ from sentry_sdk.opentelemetry.utils import (
|
|
|
29
29
|
get_profile_context,
|
|
30
30
|
get_sentry_meta,
|
|
31
31
|
set_sentry_meta,
|
|
32
|
+
delete_sentry_meta,
|
|
32
33
|
)
|
|
33
34
|
from sentry_sdk.profiler.continuous_profiler import (
|
|
34
35
|
try_autostart_continuous_profiler,
|
|
@@ -36,6 +37,7 @@ from sentry_sdk.profiler.continuous_profiler import (
|
|
|
36
37
|
try_profile_lifecycle_trace_start,
|
|
37
38
|
)
|
|
38
39
|
from sentry_sdk.profiler.transaction_profiler import Profile
|
|
40
|
+
from sentry_sdk.utils import safe_str
|
|
39
41
|
from sentry_sdk._types import TYPE_CHECKING
|
|
40
42
|
|
|
41
43
|
if TYPE_CHECKING:
|
|
@@ -51,30 +53,24 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
51
53
|
Converts OTel spans into Sentry spans so they can be sent to the Sentry backend.
|
|
52
54
|
"""
|
|
53
55
|
|
|
54
|
-
def __new__(cls):
|
|
55
|
-
# type: () -> SentrySpanProcessor
|
|
56
|
+
def __new__(cls) -> SentrySpanProcessor:
|
|
56
57
|
if not hasattr(cls, "instance"):
|
|
57
58
|
cls.instance = super().__new__(cls)
|
|
58
59
|
|
|
59
60
|
return cls.instance
|
|
60
61
|
|
|
61
|
-
def __init__(self):
|
|
62
|
-
|
|
63
|
-
self.
|
|
64
|
-
list
|
|
65
|
-
) # type: DefaultDict[int, List[ReadableSpan]]
|
|
66
|
-
self._dropped_spans = defaultdict(lambda: 0) # type: DefaultDict[int, int]
|
|
62
|
+
def __init__(self) -> None:
|
|
63
|
+
self._children_spans: DefaultDict[int, List[ReadableSpan]] = defaultdict(list)
|
|
64
|
+
self._dropped_spans: DefaultDict[int, int] = defaultdict(lambda: 0)
|
|
67
65
|
|
|
68
|
-
def on_start(self, span, parent_context=None):
|
|
69
|
-
# type: (Span, Optional[Context]) -> None
|
|
66
|
+
def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
|
|
70
67
|
if is_sentry_span(span):
|
|
71
68
|
return
|
|
72
69
|
|
|
73
70
|
self._add_root_span(span, get_current_span(parent_context))
|
|
74
71
|
self._start_profile(span)
|
|
75
72
|
|
|
76
|
-
def on_end(self, span):
|
|
77
|
-
# type: (ReadableSpan) -> None
|
|
73
|
+
def on_end(self, span: ReadableSpan) -> None:
|
|
78
74
|
if is_sentry_span(span):
|
|
79
75
|
return
|
|
80
76
|
|
|
@@ -87,18 +83,15 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
87
83
|
self._append_child_span(span)
|
|
88
84
|
|
|
89
85
|
# TODO-neel-potel not sure we need a clear like JS
|
|
90
|
-
def shutdown(self):
|
|
91
|
-
# type: () -> None
|
|
86
|
+
def shutdown(self) -> None:
|
|
92
87
|
pass
|
|
93
88
|
|
|
94
89
|
# TODO-neel-potel change default? this is 30 sec
|
|
95
90
|
# TODO-neel-potel call this in client.flush
|
|
96
|
-
def force_flush(self, timeout_millis=30000):
|
|
97
|
-
# type: (int) -> bool
|
|
91
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
98
92
|
return True
|
|
99
93
|
|
|
100
|
-
def _add_root_span(self, span, parent_span):
|
|
101
|
-
# type: (Span, AbstractSpan) -> None
|
|
94
|
+
def _add_root_span(self, span: Span, parent_span: AbstractSpan) -> None:
|
|
102
95
|
"""
|
|
103
96
|
This is required to make Span.root_span work
|
|
104
97
|
since we can't traverse back to the root purely with otel efficiently.
|
|
@@ -111,8 +104,7 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
111
104
|
# root span points to itself
|
|
112
105
|
set_sentry_meta(span, "root_span", span)
|
|
113
106
|
|
|
114
|
-
def _start_profile(self, span):
|
|
115
|
-
# type: (Span) -> None
|
|
107
|
+
def _start_profile(self, span: Span) -> None:
|
|
116
108
|
try_autostart_continuous_profiler()
|
|
117
109
|
|
|
118
110
|
profiler_id = get_profiler_id()
|
|
@@ -147,14 +139,12 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
147
139
|
span.set_attribute(SPANDATA.PROFILER_ID, profiler_id)
|
|
148
140
|
set_sentry_meta(span, "continuous_profile", continuous_profile)
|
|
149
141
|
|
|
150
|
-
def _stop_profile(self, span):
|
|
151
|
-
# type: (ReadableSpan) -> None
|
|
142
|
+
def _stop_profile(self, span: ReadableSpan) -> None:
|
|
152
143
|
continuous_profiler = get_sentry_meta(span, "continuous_profile")
|
|
153
144
|
if continuous_profiler:
|
|
154
145
|
continuous_profiler.stop()
|
|
155
146
|
|
|
156
|
-
def _flush_root_span(self, span):
|
|
157
|
-
# type: (ReadableSpan) -> None
|
|
147
|
+
def _flush_root_span(self, span: ReadableSpan) -> None:
|
|
158
148
|
transaction_event = self._root_span_to_transaction_event(span)
|
|
159
149
|
if not transaction_event:
|
|
160
150
|
return
|
|
@@ -173,9 +163,9 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
173
163
|
# TODO-neel-potel sort and cutoff max spans
|
|
174
164
|
|
|
175
165
|
sentry_sdk.capture_event(transaction_event)
|
|
166
|
+
self._cleanup_references([span] + collected_spans)
|
|
176
167
|
|
|
177
|
-
def _append_child_span(self, span):
|
|
178
|
-
# type: (ReadableSpan) -> None
|
|
168
|
+
def _append_child_span(self, span: ReadableSpan) -> None:
|
|
179
169
|
if not span.parent:
|
|
180
170
|
return
|
|
181
171
|
|
|
@@ -190,14 +180,13 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
190
180
|
else:
|
|
191
181
|
self._dropped_spans[span.parent.span_id] += 1
|
|
192
182
|
|
|
193
|
-
def _collect_children(self, span):
|
|
194
|
-
# type: (ReadableSpan) -> tuple[List[ReadableSpan], int]
|
|
183
|
+
def _collect_children(self, span: ReadableSpan) -> tuple[List[ReadableSpan], int]:
|
|
195
184
|
if not span.context:
|
|
196
185
|
return [], 0
|
|
197
186
|
|
|
198
187
|
children = []
|
|
199
188
|
dropped_spans = 0
|
|
200
|
-
bfs_queue = deque()
|
|
189
|
+
bfs_queue: Deque[int] = deque()
|
|
201
190
|
bfs_queue.append(span.context.span_id)
|
|
202
191
|
|
|
203
192
|
while bfs_queue:
|
|
@@ -213,8 +202,7 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
213
202
|
|
|
214
203
|
# we construct the event from scratch here
|
|
215
204
|
# and not use the current Transaction class for easier refactoring
|
|
216
|
-
def _root_span_to_transaction_event(self, span):
|
|
217
|
-
# type: (ReadableSpan) -> Optional[Event]
|
|
205
|
+
def _root_span_to_transaction_event(self, span: ReadableSpan) -> Optional[Event]:
|
|
218
206
|
if not span.context:
|
|
219
207
|
return None
|
|
220
208
|
|
|
@@ -231,10 +219,8 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
231
219
|
if profile_context:
|
|
232
220
|
contexts["profile"] = profile_context
|
|
233
221
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if http_status:
|
|
237
|
-
contexts["response"] = {"status_code": http_status}
|
|
222
|
+
if span_data.http_status:
|
|
223
|
+
contexts["response"] = {"status_code": span_data.http_status}
|
|
238
224
|
|
|
239
225
|
if span.resource.attributes:
|
|
240
226
|
contexts[OTEL_SENTRY_CONTEXT] = {"resource": dict(span.resource.attributes)}
|
|
@@ -242,30 +228,26 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
242
228
|
event.update(
|
|
243
229
|
{
|
|
244
230
|
"type": "transaction",
|
|
245
|
-
"transaction": transaction_name or description,
|
|
231
|
+
"transaction": transaction_name or span_data.description,
|
|
246
232
|
"transaction_info": {"source": transaction_source or "custom"},
|
|
247
233
|
"contexts": contexts,
|
|
248
234
|
}
|
|
249
235
|
)
|
|
250
236
|
|
|
251
|
-
profile =
|
|
252
|
-
if profile:
|
|
237
|
+
profile = get_sentry_meta(span, "profile")
|
|
238
|
+
if profile is not None and isinstance(profile, Profile):
|
|
253
239
|
profile.__exit__(None, None, None)
|
|
254
240
|
if profile.valid():
|
|
255
241
|
event["profile"] = profile
|
|
256
|
-
set_sentry_meta(span, "profile", None)
|
|
257
242
|
|
|
258
243
|
return event
|
|
259
244
|
|
|
260
|
-
def _span_to_json(self, span):
|
|
261
|
-
# type: (ReadableSpan) -> Optional[dict[str, Any]]
|
|
245
|
+
def _span_to_json(self, span: ReadableSpan) -> Optional[dict[str, Any]]:
|
|
262
246
|
if not span.context:
|
|
263
247
|
return None
|
|
264
248
|
|
|
265
|
-
#
|
|
266
|
-
span_json =
|
|
267
|
-
"dict[str, Any]", self._common_span_transaction_attributes_as_json(span)
|
|
268
|
-
)
|
|
249
|
+
# need to ignore the type here due to TypedDict nonsense
|
|
250
|
+
span_json: Optional[dict[str, Any]] = self._common_span_transaction_attributes_as_json(span) # type: ignore
|
|
269
251
|
if span_json is None:
|
|
270
252
|
return None
|
|
271
253
|
|
|
@@ -273,19 +255,21 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
273
255
|
span_id = format_span_id(span.context.span_id)
|
|
274
256
|
parent_span_id = format_span_id(span.parent.span_id) if span.parent else None
|
|
275
257
|
|
|
276
|
-
|
|
258
|
+
span_data = extract_span_data(span)
|
|
277
259
|
|
|
278
260
|
span_json.update(
|
|
279
261
|
{
|
|
280
262
|
"trace_id": trace_id,
|
|
281
263
|
"span_id": span_id,
|
|
282
|
-
"
|
|
283
|
-
"
|
|
284
|
-
"status": status,
|
|
285
|
-
"origin": origin or DEFAULT_SPAN_ORIGIN,
|
|
264
|
+
"description": span_data.description,
|
|
265
|
+
"origin": span_data.origin or DEFAULT_SPAN_ORIGIN,
|
|
286
266
|
}
|
|
287
267
|
)
|
|
288
268
|
|
|
269
|
+
if span_data.op:
|
|
270
|
+
span_json["op"] = span_data.op
|
|
271
|
+
if span_data.status:
|
|
272
|
+
span_json["status"] = span_data.status
|
|
289
273
|
if parent_span_id:
|
|
290
274
|
span_json["parent_span_id"] = parent_span_id
|
|
291
275
|
|
|
@@ -298,24 +282,30 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
298
282
|
|
|
299
283
|
return span_json
|
|
300
284
|
|
|
301
|
-
def _common_span_transaction_attributes_as_json(
|
|
302
|
-
|
|
285
|
+
def _common_span_transaction_attributes_as_json(
|
|
286
|
+
self, span: ReadableSpan
|
|
287
|
+
) -> Optional[Event]:
|
|
303
288
|
if not span.start_time or not span.end_time:
|
|
304
289
|
return None
|
|
305
290
|
|
|
306
|
-
common_json = {
|
|
291
|
+
common_json: Event = {
|
|
307
292
|
"start_timestamp": convert_from_otel_timestamp(span.start_time),
|
|
308
293
|
"timestamp": convert_from_otel_timestamp(span.end_time),
|
|
309
|
-
}
|
|
294
|
+
}
|
|
310
295
|
|
|
311
296
|
tags = extract_span_attributes(span, SentrySpanAttribute.TAG)
|
|
312
297
|
if tags:
|
|
313
|
-
common_json["tags"] =
|
|
298
|
+
common_json["tags"] = {
|
|
299
|
+
tag: safe_str(tag_value) for tag, tag_value in tags.items()
|
|
300
|
+
}
|
|
314
301
|
|
|
315
302
|
return common_json
|
|
316
303
|
|
|
317
|
-
def
|
|
318
|
-
|
|
304
|
+
def _cleanup_references(self, spans: List[ReadableSpan]) -> None:
|
|
305
|
+
for span in spans:
|
|
306
|
+
delete_sentry_meta(span)
|
|
307
|
+
|
|
308
|
+
def _log_debug_info(self) -> None:
|
|
319
309
|
import pprint
|
|
320
310
|
|
|
321
311
|
pprint.pprint(
|