sentry-sdk 3.0.0a2__py2.py3-none-any.whl → 3.0.0a4__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 +4 -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 +9 -16
- sentry_sdk/_werkzeug.py +5 -7
- sentry_sdk/ai/monitoring.py +45 -33
- sentry_sdk/ai/utils.py +8 -5
- sentry_sdk/api.py +91 -87
- sentry_sdk/attachments.py +10 -12
- sentry_sdk/client.py +119 -159
- sentry_sdk/consts.py +432 -223
- 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 +16 -18
- sentry_sdk/integrations/_wsgi_common.py +22 -33
- sentry_sdk/integrations/aiohttp.py +33 -31
- sentry_sdk/integrations/anthropic.py +43 -38
- sentry_sdk/integrations/argv.py +3 -4
- sentry_sdk/integrations/ariadne.py +16 -18
- sentry_sdk/integrations/arq.py +20 -29
- sentry_sdk/integrations/asgi.py +63 -37
- sentry_sdk/integrations/asyncio.py +15 -17
- sentry_sdk/integrations/asyncpg.py +1 -1
- 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 +20 -18
- sentry_sdk/integrations/bottle.py +25 -34
- sentry_sdk/integrations/celery/__init__.py +40 -59
- sentry_sdk/integrations/celery/beat.py +22 -26
- sentry_sdk/integrations/celery/utils.py +15 -17
- sentry_sdk/integrations/chalice.py +8 -10
- sentry_sdk/integrations/clickhouse_driver.py +22 -32
- sentry_sdk/integrations/cloud_resource_context.py +9 -16
- sentry_sdk/integrations/cohere.py +19 -25
- sentry_sdk/integrations/dedupe.py +5 -8
- sentry_sdk/integrations/django/__init__.py +69 -74
- sentry_sdk/integrations/django/asgi.py +25 -33
- sentry_sdk/integrations/django/caching.py +24 -20
- sentry_sdk/integrations/django/middleware.py +18 -21
- sentry_sdk/integrations/django/signals_handlers.py +12 -11
- sentry_sdk/integrations/django/templates.py +21 -18
- sentry_sdk/integrations/django/transactions.py +16 -11
- sentry_sdk/integrations/django/views.py +8 -12
- 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 +7 -20
- sentry_sdk/integrations/gql.py +16 -17
- sentry_sdk/integrations/graphene.py +14 -13
- sentry_sdk/integrations/grpc/__init__.py +3 -2
- sentry_sdk/integrations/grpc/aio/client.py +2 -2
- sentry_sdk/integrations/grpc/aio/server.py +15 -14
- sentry_sdk/integrations/grpc/client.py +21 -11
- sentry_sdk/integrations/grpc/consts.py +2 -0
- sentry_sdk/integrations/grpc/server.py +12 -8
- sentry_sdk/integrations/httpx.py +11 -14
- sentry_sdk/integrations/huey.py +14 -21
- sentry_sdk/integrations/huggingface_hub.py +17 -17
- sentry_sdk/integrations/langchain.py +204 -114
- sentry_sdk/integrations/launchdarkly.py +13 -10
- sentry_sdk/integrations/litestar.py +40 -38
- sentry_sdk/integrations/logging.py +29 -36
- sentry_sdk/integrations/loguru.py +16 -20
- sentry_sdk/integrations/modules.py +3 -4
- sentry_sdk/integrations/openai.py +421 -204
- 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 +153 -0
- sentry_sdk/integrations/openfeature.py +12 -8
- sentry_sdk/integrations/pure_eval.py +6 -10
- sentry_sdk/integrations/pymongo.py +14 -18
- 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 +18 -12
- sentry_sdk/integrations/redis/_sync_common.py +16 -15
- 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 +10 -8
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
- sentry_sdk/integrations/redis/utils.py +21 -22
- sentry_sdk/integrations/rq.py +13 -16
- sentry_sdk/integrations/rust_tracing.py +10 -7
- sentry_sdk/integrations/sanic.py +34 -46
- sentry_sdk/integrations/serverless.py +22 -27
- sentry_sdk/integrations/socket.py +29 -17
- 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 +89 -93
- sentry_sdk/integrations/starlite.py +31 -37
- sentry_sdk/integrations/statsig.py +5 -4
- sentry_sdk/integrations/stdlib.py +32 -28
- sentry_sdk/integrations/strawberry.py +63 -50
- sentry_sdk/integrations/sys_exit.py +7 -11
- sentry_sdk/integrations/threading.py +13 -15
- 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 +13 -9
- sentry_sdk/monitor.py +16 -28
- sentry_sdk/opentelemetry/consts.py +11 -4
- sentry_sdk/opentelemetry/contextvars_context.py +17 -15
- sentry_sdk/opentelemetry/propagator.py +38 -21
- sentry_sdk/opentelemetry/sampler.py +51 -34
- sentry_sdk/opentelemetry/scope.py +46 -37
- sentry_sdk/opentelemetry/span_processor.py +43 -59
- sentry_sdk/opentelemetry/tracing.py +32 -12
- sentry_sdk/opentelemetry/utils.py +180 -196
- 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 -264
- sentry_sdk/scrubber.py +22 -26
- sentry_sdk/serializer.py +48 -65
- sentry_sdk/session.py +44 -61
- sentry_sdk/sessions.py +35 -49
- sentry_sdk/spotlight.py +15 -21
- sentry_sdk/tracing.py +118 -184
- sentry_sdk/tracing_utils.py +103 -123
- sentry_sdk/transport.py +131 -157
- sentry_sdk/utils.py +278 -309
- sentry_sdk/worker.py +16 -28
- {sentry_sdk-3.0.0a2.dist-info → sentry_sdk-3.0.0a4.dist-info}/METADATA +1 -1
- sentry_sdk-3.0.0a4.dist-info/RECORD +168 -0
- sentry_sdk-3.0.0a2.dist-info/RECORD +0 -154
- {sentry_sdk-3.0.0a2.dist-info → sentry_sdk-3.0.0a4.dist-info}/WHEEL +0 -0
- {sentry_sdk-3.0.0a2.dist-info → sentry_sdk-3.0.0a4.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a2.dist-info → sentry_sdk-3.0.0a4.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-3.0.0a2.dist-info → sentry_sdk-3.0.0a4.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
|
|
|
@@ -15,6 +15,7 @@ from opentelemetry.trace import (
|
|
|
15
15
|
TraceFlags,
|
|
16
16
|
TraceState,
|
|
17
17
|
use_span,
|
|
18
|
+
INVALID_SPAN,
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
from sentry_sdk.opentelemetry.consts import (
|
|
@@ -24,9 +25,6 @@ from sentry_sdk.opentelemetry.consts import (
|
|
|
24
25
|
SENTRY_USE_ISOLATION_SCOPE_KEY,
|
|
25
26
|
TRACESTATE_SAMPLED_KEY,
|
|
26
27
|
)
|
|
27
|
-
from sentry_sdk.opentelemetry.contextvars_context import (
|
|
28
|
-
SentryContextVarsRuntimeContext,
|
|
29
|
-
)
|
|
30
28
|
from sentry_sdk.opentelemetry.utils import trace_state_from_baggage
|
|
31
29
|
from sentry_sdk.scope import Scope, ScopeType
|
|
32
30
|
from sentry_sdk.tracing import Span
|
|
@@ -38,26 +36,21 @@ if TYPE_CHECKING:
|
|
|
38
36
|
|
|
39
37
|
class PotelScope(Scope):
|
|
40
38
|
@classmethod
|
|
41
|
-
def _get_scopes(cls):
|
|
42
|
-
# type: () -> Optional[Tuple[PotelScope, PotelScope]]
|
|
39
|
+
def _get_scopes(cls) -> Optional[Tuple[PotelScope, PotelScope]]:
|
|
43
40
|
"""
|
|
44
41
|
Returns the current scopes tuple on the otel context. Internal use only.
|
|
45
42
|
"""
|
|
46
|
-
return
|
|
47
|
-
"Optional[Tuple[PotelScope, PotelScope]]", get_value(SENTRY_SCOPES_KEY)
|
|
48
|
-
)
|
|
43
|
+
return validate_scopes(get_value(SENTRY_SCOPES_KEY))
|
|
49
44
|
|
|
50
45
|
@classmethod
|
|
51
|
-
def get_current_scope(cls):
|
|
52
|
-
# type: () -> PotelScope
|
|
46
|
+
def get_current_scope(cls) -> PotelScope:
|
|
53
47
|
"""
|
|
54
48
|
Returns the current scope.
|
|
55
49
|
"""
|
|
56
50
|
return cls._get_current_scope() or _INITIAL_CURRENT_SCOPE
|
|
57
51
|
|
|
58
52
|
@classmethod
|
|
59
|
-
def _get_current_scope(cls):
|
|
60
|
-
# type: () -> Optional[PotelScope]
|
|
53
|
+
def _get_current_scope(cls) -> Optional[PotelScope]:
|
|
61
54
|
"""
|
|
62
55
|
Returns the current scope without creating a new one. Internal use only.
|
|
63
56
|
"""
|
|
@@ -65,16 +58,14 @@ class PotelScope(Scope):
|
|
|
65
58
|
return scopes[0] if scopes else None
|
|
66
59
|
|
|
67
60
|
@classmethod
|
|
68
|
-
def get_isolation_scope(cls):
|
|
69
|
-
# type: () -> PotelScope
|
|
61
|
+
def get_isolation_scope(cls) -> PotelScope:
|
|
70
62
|
"""
|
|
71
63
|
Returns the isolation scope.
|
|
72
64
|
"""
|
|
73
65
|
return cls._get_isolation_scope() or _INITIAL_ISOLATION_SCOPE
|
|
74
66
|
|
|
75
67
|
@classmethod
|
|
76
|
-
def _get_isolation_scope(cls):
|
|
77
|
-
# type: () -> Optional[PotelScope]
|
|
68
|
+
def _get_isolation_scope(cls) -> Optional[PotelScope]:
|
|
78
69
|
"""
|
|
79
70
|
Returns the isolation scope without creating a new one. Internal use only.
|
|
80
71
|
"""
|
|
@@ -82,8 +73,9 @@ class PotelScope(Scope):
|
|
|
82
73
|
return scopes[1] if scopes else None
|
|
83
74
|
|
|
84
75
|
@contextmanager
|
|
85
|
-
def continue_trace(
|
|
86
|
-
|
|
76
|
+
def continue_trace(
|
|
77
|
+
self, environ_or_headers: Dict[str, Any]
|
|
78
|
+
) -> Generator[None, None, None]:
|
|
87
79
|
"""
|
|
88
80
|
Sets the propagation context from environment or headers to continue an incoming trace.
|
|
89
81
|
Any span started within this context manager will use the same trace_id, parent_span_id
|
|
@@ -98,8 +90,16 @@ class PotelScope(Scope):
|
|
|
98
90
|
with use_span(NonRecordingSpan(span_context)):
|
|
99
91
|
yield
|
|
100
92
|
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
@contextmanager
|
|
94
|
+
def new_trace(self) -> Generator[None, None, None]:
|
|
95
|
+
"""
|
|
96
|
+
Force creation of a new trace.
|
|
97
|
+
"""
|
|
98
|
+
self.generate_propagation_context()
|
|
99
|
+
with use_span(INVALID_SPAN):
|
|
100
|
+
yield
|
|
101
|
+
|
|
102
|
+
def _incoming_otel_span_context(self) -> Optional[SpanContext]:
|
|
103
103
|
if self._propagation_context is None:
|
|
104
104
|
return None
|
|
105
105
|
# If sentry-trace extraction didn't have a parent_span_id, we don't have an upstream header
|
|
@@ -132,8 +132,7 @@ class PotelScope(Scope):
|
|
|
132
132
|
|
|
133
133
|
return span_context
|
|
134
134
|
|
|
135
|
-
def start_transaction(self, **kwargs):
|
|
136
|
-
# type: (Any) -> Span
|
|
135
|
+
def start_transaction(self, **kwargs: Any) -> Span:
|
|
137
136
|
"""
|
|
138
137
|
.. deprecated:: 3.0.0
|
|
139
138
|
This function is deprecated and will be removed in a future release.
|
|
@@ -146,8 +145,7 @@ class PotelScope(Scope):
|
|
|
146
145
|
)
|
|
147
146
|
return self.start_span(**kwargs)
|
|
148
147
|
|
|
149
|
-
def start_span(self, **kwargs):
|
|
150
|
-
# type: (Any) -> Span
|
|
148
|
+
def start_span(self, **kwargs: Any) -> Span:
|
|
151
149
|
return Span(**kwargs)
|
|
152
150
|
|
|
153
151
|
|
|
@@ -155,8 +153,7 @@ _INITIAL_CURRENT_SCOPE = PotelScope(ty=ScopeType.CURRENT)
|
|
|
155
153
|
_INITIAL_ISOLATION_SCOPE = PotelScope(ty=ScopeType.ISOLATION)
|
|
156
154
|
|
|
157
155
|
|
|
158
|
-
def setup_initial_scopes():
|
|
159
|
-
# type: () -> None
|
|
156
|
+
def setup_initial_scopes() -> None:
|
|
160
157
|
global _INITIAL_CURRENT_SCOPE, _INITIAL_ISOLATION_SCOPE
|
|
161
158
|
_INITIAL_CURRENT_SCOPE = PotelScope(ty=ScopeType.CURRENT)
|
|
162
159
|
_INITIAL_ISOLATION_SCOPE = PotelScope(ty=ScopeType.ISOLATION)
|
|
@@ -165,17 +162,18 @@ def setup_initial_scopes():
|
|
|
165
162
|
attach(set_value(SENTRY_SCOPES_KEY, scopes))
|
|
166
163
|
|
|
167
164
|
|
|
168
|
-
def setup_scope_context_management():
|
|
169
|
-
# type: () -> None
|
|
165
|
+
def setup_scope_context_management() -> None:
|
|
170
166
|
import opentelemetry.context
|
|
167
|
+
from sentry_sdk.opentelemetry.contextvars_context import (
|
|
168
|
+
SentryContextVarsRuntimeContext,
|
|
169
|
+
)
|
|
171
170
|
|
|
172
171
|
opentelemetry.context._RUNTIME_CONTEXT = SentryContextVarsRuntimeContext()
|
|
173
172
|
setup_initial_scopes()
|
|
174
173
|
|
|
175
174
|
|
|
176
175
|
@contextmanager
|
|
177
|
-
def isolation_scope():
|
|
178
|
-
# type: () -> Generator[PotelScope, None, None]
|
|
176
|
+
def isolation_scope() -> Generator[PotelScope, None, None]:
|
|
179
177
|
context = set_value(SENTRY_FORK_ISOLATION_SCOPE_KEY, True)
|
|
180
178
|
token = attach(context)
|
|
181
179
|
try:
|
|
@@ -185,8 +183,7 @@ def isolation_scope():
|
|
|
185
183
|
|
|
186
184
|
|
|
187
185
|
@contextmanager
|
|
188
|
-
def new_scope():
|
|
189
|
-
# type: () -> Generator[PotelScope, None, None]
|
|
186
|
+
def new_scope() -> Generator[PotelScope, None, None]:
|
|
190
187
|
token = attach(get_current())
|
|
191
188
|
try:
|
|
192
189
|
yield PotelScope.get_current_scope()
|
|
@@ -195,8 +192,7 @@ def new_scope():
|
|
|
195
192
|
|
|
196
193
|
|
|
197
194
|
@contextmanager
|
|
198
|
-
def use_scope(scope):
|
|
199
|
-
# type: (PotelScope) -> Generator[PotelScope, None, None]
|
|
195
|
+
def use_scope(scope: PotelScope) -> Generator[PotelScope, None, None]:
|
|
200
196
|
context = set_value(SENTRY_USE_CURRENT_SCOPE_KEY, scope)
|
|
201
197
|
token = attach(context)
|
|
202
198
|
|
|
@@ -207,8 +203,9 @@ def use_scope(scope):
|
|
|
207
203
|
|
|
208
204
|
|
|
209
205
|
@contextmanager
|
|
210
|
-
def use_isolation_scope(
|
|
211
|
-
|
|
206
|
+
def use_isolation_scope(
|
|
207
|
+
isolation_scope: PotelScope,
|
|
208
|
+
) -> Generator[PotelScope, None, None]:
|
|
212
209
|
context = set_value(SENTRY_USE_ISOLATION_SCOPE_KEY, isolation_scope)
|
|
213
210
|
token = attach(context)
|
|
214
211
|
|
|
@@ -216,3 +213,15 @@ def use_isolation_scope(isolation_scope):
|
|
|
216
213
|
yield isolation_scope
|
|
217
214
|
finally:
|
|
218
215
|
detach(token)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def validate_scopes(scopes: Any) -> Optional[Tuple[PotelScope, PotelScope]]:
|
|
219
|
+
if (
|
|
220
|
+
isinstance(scopes, tuple)
|
|
221
|
+
and len(scopes) == 2
|
|
222
|
+
and isinstance(scopes[0], PotelScope)
|
|
223
|
+
and isinstance(scopes[1], PotelScope)
|
|
224
|
+
):
|
|
225
|
+
return scopes
|
|
226
|
+
else:
|
|
227
|
+
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,
|
|
@@ -37,6 +37,7 @@ from sentry_sdk.profiler.continuous_profiler import (
|
|
|
37
37
|
try_profile_lifecycle_trace_start,
|
|
38
38
|
)
|
|
39
39
|
from sentry_sdk.profiler.transaction_profiler import Profile
|
|
40
|
+
from sentry_sdk.utils import safe_str
|
|
40
41
|
from sentry_sdk._types import TYPE_CHECKING
|
|
41
42
|
|
|
42
43
|
if TYPE_CHECKING:
|
|
@@ -52,30 +53,24 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
52
53
|
Converts OTel spans into Sentry spans so they can be sent to the Sentry backend.
|
|
53
54
|
"""
|
|
54
55
|
|
|
55
|
-
def __new__(cls):
|
|
56
|
-
# type: () -> SentrySpanProcessor
|
|
56
|
+
def __new__(cls) -> SentrySpanProcessor:
|
|
57
57
|
if not hasattr(cls, "instance"):
|
|
58
58
|
cls.instance = super().__new__(cls)
|
|
59
59
|
|
|
60
60
|
return cls.instance
|
|
61
61
|
|
|
62
|
-
def __init__(self):
|
|
63
|
-
|
|
64
|
-
self.
|
|
65
|
-
list
|
|
66
|
-
) # type: DefaultDict[int, List[ReadableSpan]]
|
|
67
|
-
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)
|
|
68
65
|
|
|
69
|
-
def on_start(self, span, parent_context=None):
|
|
70
|
-
# type: (Span, Optional[Context]) -> None
|
|
66
|
+
def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None:
|
|
71
67
|
if is_sentry_span(span):
|
|
72
68
|
return
|
|
73
69
|
|
|
74
70
|
self._add_root_span(span, get_current_span(parent_context))
|
|
75
71
|
self._start_profile(span)
|
|
76
72
|
|
|
77
|
-
def on_end(self, span):
|
|
78
|
-
# type: (ReadableSpan) -> None
|
|
73
|
+
def on_end(self, span: ReadableSpan) -> None:
|
|
79
74
|
if is_sentry_span(span):
|
|
80
75
|
return
|
|
81
76
|
|
|
@@ -88,18 +83,15 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
88
83
|
self._append_child_span(span)
|
|
89
84
|
|
|
90
85
|
# TODO-neel-potel not sure we need a clear like JS
|
|
91
|
-
def shutdown(self):
|
|
92
|
-
# type: () -> None
|
|
86
|
+
def shutdown(self) -> None:
|
|
93
87
|
pass
|
|
94
88
|
|
|
95
89
|
# TODO-neel-potel change default? this is 30 sec
|
|
96
90
|
# TODO-neel-potel call this in client.flush
|
|
97
|
-
def force_flush(self, timeout_millis=30000):
|
|
98
|
-
# type: (int) -> bool
|
|
91
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
99
92
|
return True
|
|
100
93
|
|
|
101
|
-
def _add_root_span(self, span, parent_span):
|
|
102
|
-
# type: (Span, AbstractSpan) -> None
|
|
94
|
+
def _add_root_span(self, span: Span, parent_span: AbstractSpan) -> None:
|
|
103
95
|
"""
|
|
104
96
|
This is required to make Span.root_span work
|
|
105
97
|
since we can't traverse back to the root purely with otel efficiently.
|
|
@@ -112,8 +104,7 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
112
104
|
# root span points to itself
|
|
113
105
|
set_sentry_meta(span, "root_span", span)
|
|
114
106
|
|
|
115
|
-
def _start_profile(self, span):
|
|
116
|
-
# type: (Span) -> None
|
|
107
|
+
def _start_profile(self, span: Span) -> None:
|
|
117
108
|
try_autostart_continuous_profiler()
|
|
118
109
|
|
|
119
110
|
profiler_id = get_profiler_id()
|
|
@@ -148,14 +139,12 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
148
139
|
span.set_attribute(SPANDATA.PROFILER_ID, profiler_id)
|
|
149
140
|
set_sentry_meta(span, "continuous_profile", continuous_profile)
|
|
150
141
|
|
|
151
|
-
def _stop_profile(self, span):
|
|
152
|
-
# type: (ReadableSpan) -> None
|
|
142
|
+
def _stop_profile(self, span: ReadableSpan) -> None:
|
|
153
143
|
continuous_profiler = get_sentry_meta(span, "continuous_profile")
|
|
154
144
|
if continuous_profiler:
|
|
155
145
|
continuous_profiler.stop()
|
|
156
146
|
|
|
157
|
-
def _flush_root_span(self, span):
|
|
158
|
-
# type: (ReadableSpan) -> None
|
|
147
|
+
def _flush_root_span(self, span: ReadableSpan) -> None:
|
|
159
148
|
transaction_event = self._root_span_to_transaction_event(span)
|
|
160
149
|
if not transaction_event:
|
|
161
150
|
return
|
|
@@ -176,8 +165,7 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
176
165
|
sentry_sdk.capture_event(transaction_event)
|
|
177
166
|
self._cleanup_references([span] + collected_spans)
|
|
178
167
|
|
|
179
|
-
def _append_child_span(self, span):
|
|
180
|
-
# type: (ReadableSpan) -> None
|
|
168
|
+
def _append_child_span(self, span: ReadableSpan) -> None:
|
|
181
169
|
if not span.parent:
|
|
182
170
|
return
|
|
183
171
|
|
|
@@ -192,14 +180,13 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
192
180
|
else:
|
|
193
181
|
self._dropped_spans[span.parent.span_id] += 1
|
|
194
182
|
|
|
195
|
-
def _collect_children(self, span):
|
|
196
|
-
# type: (ReadableSpan) -> tuple[List[ReadableSpan], int]
|
|
183
|
+
def _collect_children(self, span: ReadableSpan) -> tuple[List[ReadableSpan], int]:
|
|
197
184
|
if not span.context:
|
|
198
185
|
return [], 0
|
|
199
186
|
|
|
200
187
|
children = []
|
|
201
188
|
dropped_spans = 0
|
|
202
|
-
bfs_queue = deque()
|
|
189
|
+
bfs_queue: Deque[int] = deque()
|
|
203
190
|
bfs_queue.append(span.context.span_id)
|
|
204
191
|
|
|
205
192
|
while bfs_queue:
|
|
@@ -215,8 +202,7 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
215
202
|
|
|
216
203
|
# we construct the event from scratch here
|
|
217
204
|
# and not use the current Transaction class for easier refactoring
|
|
218
|
-
def _root_span_to_transaction_event(self, span):
|
|
219
|
-
# type: (ReadableSpan) -> Optional[Event]
|
|
205
|
+
def _root_span_to_transaction_event(self, span: ReadableSpan) -> Optional[Event]:
|
|
220
206
|
if not span.context:
|
|
221
207
|
return None
|
|
222
208
|
|
|
@@ -233,10 +219,8 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
233
219
|
if profile_context:
|
|
234
220
|
contexts["profile"] = profile_context
|
|
235
221
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if http_status:
|
|
239
|
-
contexts["response"] = {"status_code": http_status}
|
|
222
|
+
if span_data.http_status:
|
|
223
|
+
contexts["response"] = {"status_code": span_data.http_status}
|
|
240
224
|
|
|
241
225
|
if span.resource.attributes:
|
|
242
226
|
contexts[OTEL_SENTRY_CONTEXT] = {"resource": dict(span.resource.attributes)}
|
|
@@ -244,29 +228,26 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
244
228
|
event.update(
|
|
245
229
|
{
|
|
246
230
|
"type": "transaction",
|
|
247
|
-
"transaction": transaction_name or description,
|
|
231
|
+
"transaction": transaction_name or span_data.description,
|
|
248
232
|
"transaction_info": {"source": transaction_source or "custom"},
|
|
249
233
|
"contexts": contexts,
|
|
250
234
|
}
|
|
251
235
|
)
|
|
252
236
|
|
|
253
|
-
profile =
|
|
254
|
-
if profile:
|
|
237
|
+
profile = get_sentry_meta(span, "profile")
|
|
238
|
+
if profile is not None and isinstance(profile, Profile):
|
|
255
239
|
profile.__exit__(None, None, None)
|
|
256
240
|
if profile.valid():
|
|
257
241
|
event["profile"] = profile
|
|
258
242
|
|
|
259
243
|
return event
|
|
260
244
|
|
|
261
|
-
def _span_to_json(self, span):
|
|
262
|
-
# type: (ReadableSpan) -> Optional[dict[str, Any]]
|
|
245
|
+
def _span_to_json(self, span: ReadableSpan) -> Optional[dict[str, Any]]:
|
|
263
246
|
if not span.context:
|
|
264
247
|
return None
|
|
265
248
|
|
|
266
|
-
#
|
|
267
|
-
span_json =
|
|
268
|
-
"dict[str, Any]", self._common_span_transaction_attributes_as_json(span)
|
|
269
|
-
)
|
|
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
|
|
270
251
|
if span_json is None:
|
|
271
252
|
return None
|
|
272
253
|
|
|
@@ -274,19 +255,21 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
274
255
|
span_id = format_span_id(span.context.span_id)
|
|
275
256
|
parent_span_id = format_span_id(span.parent.span_id) if span.parent else None
|
|
276
257
|
|
|
277
|
-
|
|
258
|
+
span_data = extract_span_data(span)
|
|
278
259
|
|
|
279
260
|
span_json.update(
|
|
280
261
|
{
|
|
281
262
|
"trace_id": trace_id,
|
|
282
263
|
"span_id": span_id,
|
|
283
|
-
"
|
|
284
|
-
"
|
|
285
|
-
"status": status,
|
|
286
|
-
"origin": origin or DEFAULT_SPAN_ORIGIN,
|
|
264
|
+
"description": span_data.description,
|
|
265
|
+
"origin": span_data.origin or DEFAULT_SPAN_ORIGIN,
|
|
287
266
|
}
|
|
288
267
|
)
|
|
289
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
|
|
290
273
|
if parent_span_id:
|
|
291
274
|
span_json["parent_span_id"] = parent_span_id
|
|
292
275
|
|
|
@@ -299,29 +282,30 @@ class SentrySpanProcessor(SpanProcessor):
|
|
|
299
282
|
|
|
300
283
|
return span_json
|
|
301
284
|
|
|
302
|
-
def _common_span_transaction_attributes_as_json(
|
|
303
|
-
|
|
285
|
+
def _common_span_transaction_attributes_as_json(
|
|
286
|
+
self, span: ReadableSpan
|
|
287
|
+
) -> Optional[Event]:
|
|
304
288
|
if not span.start_time or not span.end_time:
|
|
305
289
|
return None
|
|
306
290
|
|
|
307
|
-
common_json = {
|
|
291
|
+
common_json: Event = {
|
|
308
292
|
"start_timestamp": convert_from_otel_timestamp(span.start_time),
|
|
309
293
|
"timestamp": convert_from_otel_timestamp(span.end_time),
|
|
310
|
-
}
|
|
294
|
+
}
|
|
311
295
|
|
|
312
296
|
tags = extract_span_attributes(span, SentrySpanAttribute.TAG)
|
|
313
297
|
if tags:
|
|
314
|
-
common_json["tags"] =
|
|
298
|
+
common_json["tags"] = {
|
|
299
|
+
tag: safe_str(tag_value) for tag, tag_value in tags.items()
|
|
300
|
+
}
|
|
315
301
|
|
|
316
302
|
return common_json
|
|
317
303
|
|
|
318
|
-
def _cleanup_references(self, spans):
|
|
319
|
-
# type: (List[ReadableSpan]) -> None
|
|
304
|
+
def _cleanup_references(self, spans: List[ReadableSpan]) -> None:
|
|
320
305
|
for span in spans:
|
|
321
306
|
delete_sentry_meta(span)
|
|
322
307
|
|
|
323
|
-
def _log_debug_info(self):
|
|
324
|
-
# type: () -> None
|
|
308
|
+
def _log_debug_info(self) -> None:
|
|
325
309
|
import pprint
|
|
326
310
|
|
|
327
311
|
pprint.pprint(
|