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.

Files changed (109) hide show
  1. sentry_sdk/__init__.py +3 -8
  2. sentry_sdk/_compat.py +0 -1
  3. sentry_sdk/_init_implementation.py +6 -44
  4. sentry_sdk/_types.py +2 -64
  5. sentry_sdk/ai/monitoring.py +14 -10
  6. sentry_sdk/ai/utils.py +1 -1
  7. sentry_sdk/api.py +56 -169
  8. sentry_sdk/client.py +27 -72
  9. sentry_sdk/consts.py +60 -23
  10. sentry_sdk/debug.py +0 -10
  11. sentry_sdk/envelope.py +1 -3
  12. sentry_sdk/feature_flags.py +1 -1
  13. sentry_sdk/integrations/__init__.py +4 -2
  14. sentry_sdk/integrations/_asgi_common.py +5 -6
  15. sentry_sdk/integrations/_wsgi_common.py +11 -40
  16. sentry_sdk/integrations/aiohttp.py +104 -57
  17. sentry_sdk/integrations/anthropic.py +10 -7
  18. sentry_sdk/integrations/arq.py +24 -13
  19. sentry_sdk/integrations/asgi.py +102 -83
  20. sentry_sdk/integrations/asyncio.py +1 -0
  21. sentry_sdk/integrations/asyncpg.py +45 -30
  22. sentry_sdk/integrations/aws_lambda.py +109 -92
  23. sentry_sdk/integrations/boto3.py +38 -9
  24. sentry_sdk/integrations/bottle.py +1 -1
  25. sentry_sdk/integrations/celery/__init__.py +51 -41
  26. sentry_sdk/integrations/clickhouse_driver.py +59 -28
  27. sentry_sdk/integrations/cohere.py +2 -0
  28. sentry_sdk/integrations/django/__init__.py +25 -46
  29. sentry_sdk/integrations/django/asgi.py +6 -2
  30. sentry_sdk/integrations/django/caching.py +13 -22
  31. sentry_sdk/integrations/django/middleware.py +1 -0
  32. sentry_sdk/integrations/django/signals_handlers.py +3 -1
  33. sentry_sdk/integrations/django/templates.py +8 -12
  34. sentry_sdk/integrations/django/transactions.py +1 -6
  35. sentry_sdk/integrations/django/views.py +5 -2
  36. sentry_sdk/integrations/falcon.py +7 -25
  37. sentry_sdk/integrations/fastapi.py +3 -3
  38. sentry_sdk/integrations/flask.py +1 -1
  39. sentry_sdk/integrations/gcp.py +63 -38
  40. sentry_sdk/integrations/graphene.py +6 -13
  41. sentry_sdk/integrations/grpc/aio/client.py +14 -8
  42. sentry_sdk/integrations/grpc/aio/server.py +19 -21
  43. sentry_sdk/integrations/grpc/client.py +8 -6
  44. sentry_sdk/integrations/grpc/server.py +12 -14
  45. sentry_sdk/integrations/httpx.py +47 -12
  46. sentry_sdk/integrations/huey.py +26 -22
  47. sentry_sdk/integrations/huggingface_hub.py +1 -0
  48. sentry_sdk/integrations/langchain.py +22 -15
  49. sentry_sdk/integrations/litestar.py +4 -2
  50. sentry_sdk/integrations/logging.py +7 -2
  51. sentry_sdk/integrations/openai.py +2 -0
  52. sentry_sdk/integrations/pymongo.py +18 -25
  53. sentry_sdk/integrations/pyramid.py +1 -1
  54. sentry_sdk/integrations/quart.py +3 -3
  55. sentry_sdk/integrations/ray.py +23 -17
  56. sentry_sdk/integrations/redis/_async_common.py +29 -18
  57. sentry_sdk/integrations/redis/_sync_common.py +28 -19
  58. sentry_sdk/integrations/redis/modules/caches.py +13 -10
  59. sentry_sdk/integrations/redis/modules/queries.py +14 -11
  60. sentry_sdk/integrations/redis/rb.py +4 -4
  61. sentry_sdk/integrations/redis/redis.py +6 -6
  62. sentry_sdk/integrations/redis/redis_cluster.py +18 -18
  63. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +4 -4
  64. sentry_sdk/integrations/redis/utils.py +64 -24
  65. sentry_sdk/integrations/rq.py +68 -23
  66. sentry_sdk/integrations/rust_tracing.py +28 -43
  67. sentry_sdk/integrations/sanic.py +23 -13
  68. sentry_sdk/integrations/socket.py +9 -5
  69. sentry_sdk/integrations/sqlalchemy.py +8 -8
  70. sentry_sdk/integrations/starlette.py +11 -31
  71. sentry_sdk/integrations/starlite.py +4 -2
  72. sentry_sdk/integrations/stdlib.py +56 -9
  73. sentry_sdk/integrations/strawberry.py +40 -59
  74. sentry_sdk/integrations/threading.py +10 -26
  75. sentry_sdk/integrations/tornado.py +57 -18
  76. sentry_sdk/integrations/trytond.py +4 -1
  77. sentry_sdk/integrations/wsgi.py +84 -38
  78. sentry_sdk/opentelemetry/__init__.py +9 -0
  79. sentry_sdk/opentelemetry/consts.py +33 -0
  80. sentry_sdk/opentelemetry/contextvars_context.py +81 -0
  81. sentry_sdk/{integrations/opentelemetry → opentelemetry}/propagator.py +19 -28
  82. sentry_sdk/opentelemetry/sampler.py +326 -0
  83. sentry_sdk/opentelemetry/scope.py +218 -0
  84. sentry_sdk/opentelemetry/span_processor.py +335 -0
  85. sentry_sdk/opentelemetry/tracing.py +59 -0
  86. sentry_sdk/opentelemetry/utils.py +484 -0
  87. sentry_sdk/profiler/__init__.py +0 -40
  88. sentry_sdk/profiler/continuous_profiler.py +1 -30
  89. sentry_sdk/profiler/transaction_profiler.py +5 -56
  90. sentry_sdk/scope.py +108 -361
  91. sentry_sdk/sessions.py +0 -87
  92. sentry_sdk/tracing.py +415 -1161
  93. sentry_sdk/tracing_utils.py +130 -166
  94. sentry_sdk/transport.py +4 -104
  95. sentry_sdk/utils.py +169 -152
  96. {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/METADATA +3 -5
  97. sentry_sdk-3.0.0a2.dist-info/RECORD +154 -0
  98. sentry_sdk-3.0.0a2.dist-info/entry_points.txt +2 -0
  99. sentry_sdk/hub.py +0 -739
  100. sentry_sdk/integrations/opentelemetry/__init__.py +0 -7
  101. sentry_sdk/integrations/opentelemetry/consts.py +0 -5
  102. sentry_sdk/integrations/opentelemetry/integration.py +0 -58
  103. sentry_sdk/integrations/opentelemetry/span_processor.py +0 -391
  104. sentry_sdk/metrics.py +0 -965
  105. sentry_sdk-2.30.0.dist-info/RECORD +0 -152
  106. sentry_sdk-2.30.0.dist-info/entry_points.txt +0 -2
  107. {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/WHEEL +0 -0
  108. {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/licenses/LICENSE +0 -0
  109. {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,81 @@
1
+ from typing import cast, TYPE_CHECKING
2
+
3
+ from opentelemetry.trace import get_current_span, set_span_in_context
4
+ from opentelemetry.trace.span import INVALID_SPAN
5
+ from opentelemetry.context import Context, get_value, set_value
6
+ from opentelemetry.context.contextvars_context import ContextVarsRuntimeContext
7
+
8
+ import sentry_sdk
9
+ from sentry_sdk.tracing import Span
10
+ from sentry_sdk.opentelemetry.consts import (
11
+ SENTRY_SCOPES_KEY,
12
+ SENTRY_FORK_ISOLATION_SCOPE_KEY,
13
+ SENTRY_USE_CURRENT_SCOPE_KEY,
14
+ SENTRY_USE_ISOLATION_SCOPE_KEY,
15
+ )
16
+
17
+ if TYPE_CHECKING:
18
+ from typing import Optional
19
+ from contextvars import Token
20
+ import sentry_sdk.opentelemetry.scope as scope
21
+
22
+
23
+ class SentryContextVarsRuntimeContext(ContextVarsRuntimeContext):
24
+ def attach(self, context):
25
+ # type: (Context) -> Token[Context]
26
+ scopes = get_value(SENTRY_SCOPES_KEY, context)
27
+
28
+ should_fork_isolation_scope = context.pop(
29
+ SENTRY_FORK_ISOLATION_SCOPE_KEY, False
30
+ )
31
+ should_fork_isolation_scope = cast("bool", should_fork_isolation_scope)
32
+
33
+ should_use_isolation_scope = context.pop(SENTRY_USE_ISOLATION_SCOPE_KEY, None)
34
+ should_use_isolation_scope = cast(
35
+ "Optional[scope.PotelScope]", should_use_isolation_scope
36
+ )
37
+
38
+ should_use_current_scope = context.pop(SENTRY_USE_CURRENT_SCOPE_KEY, None)
39
+ should_use_current_scope = cast(
40
+ "Optional[scope.PotelScope]", should_use_current_scope
41
+ )
42
+
43
+ if scopes:
44
+ scopes = cast("tuple[scope.PotelScope, scope.PotelScope]", scopes)
45
+ (current_scope, isolation_scope) = scopes
46
+ else:
47
+ current_scope = sentry_sdk.get_current_scope()
48
+ isolation_scope = sentry_sdk.get_isolation_scope()
49
+
50
+ new_context = context
51
+
52
+ if should_use_current_scope:
53
+ new_scope = should_use_current_scope
54
+
55
+ # the main case where we use use_scope is for
56
+ # scope propagation in the ThreadingIntegration
57
+ # so we need to carry forward the span reference explicitly too
58
+ span = should_use_current_scope.span
59
+ if span:
60
+ new_context = set_span_in_context(span._otel_span, new_context)
61
+
62
+ else:
63
+ new_scope = current_scope.fork()
64
+
65
+ # carry forward a wrapped span reference since the otel context is always the
66
+ # source of truth for the active span
67
+ current_span = get_current_span(context)
68
+ if current_span != INVALID_SPAN:
69
+ new_scope._span = Span(otel_span=get_current_span(context))
70
+
71
+ if should_use_isolation_scope:
72
+ new_isolation_scope = should_use_isolation_scope
73
+ elif should_fork_isolation_scope:
74
+ new_isolation_scope = isolation_scope.fork()
75
+ else:
76
+ new_isolation_scope = isolation_scope
77
+
78
+ new_scopes = (new_scope, new_isolation_scope)
79
+
80
+ new_context = set_value(SENTRY_SCOPES_KEY, new_scopes, new_context)
81
+ return super().attach(new_context)
@@ -1,7 +1,10 @@
1
+ from typing import cast
2
+
1
3
  from opentelemetry import trace
2
4
  from opentelemetry.context import (
3
5
  Context,
4
6
  get_current,
7
+ get_value,
5
8
  set_value,
6
9
  )
7
10
  from opentelemetry.propagators.textmap import (
@@ -18,23 +21,22 @@ from opentelemetry.trace import (
18
21
  TraceFlags,
19
22
  )
20
23
 
21
- from sentry_sdk.integrations.opentelemetry.consts import (
22
- SENTRY_BAGGAGE_KEY,
23
- SENTRY_TRACE_KEY,
24
- )
25
- from sentry_sdk.integrations.opentelemetry.span_processor import (
26
- SentrySpanProcessor,
27
- )
28
- from sentry_sdk.tracing import (
24
+ from sentry_sdk.consts import (
29
25
  BAGGAGE_HEADER_NAME,
30
26
  SENTRY_TRACE_HEADER_NAME,
31
27
  )
28
+ from sentry_sdk.opentelemetry.consts import (
29
+ SENTRY_BAGGAGE_KEY,
30
+ SENTRY_TRACE_KEY,
31
+ SENTRY_SCOPES_KEY,
32
+ )
32
33
  from sentry_sdk.tracing_utils import Baggage, extract_sentrytrace_data
33
34
 
34
35
  from typing import TYPE_CHECKING
35
36
 
36
37
  if TYPE_CHECKING:
37
38
  from typing import Optional, Set
39
+ import sentry_sdk.opentelemetry.scope as scope
38
40
 
39
41
 
40
42
  class SentryPropagator(TextMapPropagator):
@@ -47,6 +49,7 @@ class SentryPropagator(TextMapPropagator):
47
49
  if context is None:
48
50
  context = get_current()
49
51
 
52
+ # TODO-neel-potel cleanup with continue_trace / isolation_scope
50
53
  sentry_trace = getter.get(carrier, SENTRY_TRACE_HEADER_NAME)
51
54
  if not sentry_trace:
52
55
  return context
@@ -89,27 +92,15 @@ class SentryPropagator(TextMapPropagator):
89
92
  if context is None:
90
93
  context = get_current()
91
94
 
92
- current_span = trace.get_current_span(context)
93
- current_span_context = current_span.get_span_context()
94
-
95
- if not current_span_context.is_valid:
96
- return
97
-
98
- span_id = trace.format_span_id(current_span_context.span_id)
99
-
100
- span_map = SentrySpanProcessor().otel_span_map
101
- sentry_span = span_map.get(span_id, None)
102
- if not sentry_span:
103
- return
104
-
105
- setter.set(carrier, SENTRY_TRACE_HEADER_NAME, sentry_span.to_traceparent())
95
+ scopes = get_value(SENTRY_SCOPES_KEY, context)
96
+ if scopes:
97
+ scopes = cast("tuple[scope.PotelScope, scope.PotelScope]", scopes)
98
+ (current_scope, _) = scopes
106
99
 
107
- if sentry_span.containing_transaction:
108
- baggage = sentry_span.containing_transaction.get_baggage()
109
- if baggage:
110
- baggage_data = baggage.serialize()
111
- if baggage_data:
112
- setter.set(carrier, BAGGAGE_HEADER_NAME, baggage_data)
100
+ # TODO-neel-potel check trace_propagation_targets
101
+ # TODO-neel-potel test propagator works with twp
102
+ for key, value in current_scope.iter_trace_propagation_headers():
103
+ setter.set(carrier, key, value)
113
104
 
114
105
  @property
115
106
  def fields(self):
@@ -0,0 +1,326 @@
1
+ from decimal import Decimal
2
+ from typing import cast
3
+
4
+ from opentelemetry import trace
5
+ from opentelemetry.sdk.trace.sampling import Sampler, SamplingResult, Decision
6
+ from opentelemetry.trace.span import TraceState
7
+
8
+ import sentry_sdk
9
+ from sentry_sdk.opentelemetry.consts import (
10
+ TRACESTATE_SAMPLED_KEY,
11
+ TRACESTATE_SAMPLE_RAND_KEY,
12
+ TRACESTATE_SAMPLE_RATE_KEY,
13
+ SentrySpanAttribute,
14
+ )
15
+ from sentry_sdk.tracing_utils import (
16
+ _generate_sample_rand,
17
+ has_tracing_enabled,
18
+ )
19
+ from sentry_sdk.utils import is_valid_sample_rate, logger
20
+
21
+ from typing import TYPE_CHECKING
22
+
23
+ if TYPE_CHECKING:
24
+ from typing import Any, Optional, Sequence, Union
25
+ from opentelemetry.context import Context
26
+ from opentelemetry.trace import Link, SpanKind
27
+ from opentelemetry.trace.span import SpanContext
28
+ from opentelemetry.util.types import Attributes
29
+
30
+
31
+ def get_parent_sampled(parent_context, trace_id):
32
+ # type: (Optional[SpanContext], int) -> Optional[bool]
33
+ if parent_context is None:
34
+ return None
35
+
36
+ is_span_context_valid = parent_context is not None and parent_context.is_valid
37
+
38
+ # Only inherit sample rate if `traceId` is the same
39
+ if is_span_context_valid and parent_context.trace_id == trace_id:
40
+ # this is getSamplingDecision in JS
41
+ # if there was no sampling flag, defer the decision
42
+ dsc_sampled = parent_context.trace_state.get(TRACESTATE_SAMPLED_KEY)
43
+ if dsc_sampled == "deferred":
44
+ return None
45
+
46
+ if parent_context.trace_flags.sampled is not None:
47
+ return parent_context.trace_flags.sampled
48
+
49
+ if dsc_sampled == "true":
50
+ return True
51
+ elif dsc_sampled == "false":
52
+ return False
53
+
54
+ return None
55
+
56
+
57
+ def get_parent_sample_rate(parent_context, trace_id):
58
+ # type: (Optional[SpanContext], int) -> Optional[float]
59
+ if parent_context is None:
60
+ return None
61
+
62
+ is_span_context_valid = parent_context is not None and parent_context.is_valid
63
+
64
+ if is_span_context_valid and parent_context.trace_id == trace_id:
65
+ parent_sample_rate = parent_context.trace_state.get(TRACESTATE_SAMPLE_RATE_KEY)
66
+ if parent_sample_rate is None:
67
+ return None
68
+
69
+ try:
70
+ return float(parent_sample_rate)
71
+ except Exception:
72
+ return None
73
+
74
+ return None
75
+
76
+
77
+ def get_parent_sample_rand(parent_context, trace_id):
78
+ # type: (Optional[SpanContext], int) -> Optional[Decimal]
79
+ if parent_context is None:
80
+ return None
81
+
82
+ is_span_context_valid = parent_context is not None and parent_context.is_valid
83
+
84
+ if is_span_context_valid and parent_context.trace_id == trace_id:
85
+ parent_sample_rand = parent_context.trace_state.get(TRACESTATE_SAMPLE_RAND_KEY)
86
+ if parent_sample_rand is None:
87
+ return None
88
+
89
+ return Decimal(parent_sample_rand)
90
+
91
+ return None
92
+
93
+
94
+ def dropped_result(span_context, attributes, sample_rate=None, sample_rand=None):
95
+ # type: (SpanContext, Attributes, Optional[float], Optional[Decimal]) -> SamplingResult
96
+ """
97
+ React to a span getting unsampled and return a DROP SamplingResult.
98
+
99
+ Update the trace_state with the effective sampled, sample_rate and sample_rand,
100
+ record that we dropped the event for client report purposes, and return
101
+ an OTel SamplingResult with Decision.DROP.
102
+
103
+ See for more info about OTel sampling:
104
+ https://opentelemetry-python.readthedocs.io/en/latest/sdk/trace.sampling.html
105
+ """
106
+ trace_state = _update_trace_state(
107
+ span_context, sampled=False, sample_rate=sample_rate, sample_rand=sample_rand
108
+ )
109
+
110
+ is_root_span = not (span_context.is_valid and not span_context.is_remote)
111
+ if is_root_span:
112
+ # Tell Sentry why we dropped the transaction/root-span
113
+ client = sentry_sdk.get_client()
114
+ if client.monitor and client.monitor.downsample_factor > 0:
115
+ reason = "backpressure"
116
+ else:
117
+ reason = "sample_rate"
118
+
119
+ if client.transport and has_tracing_enabled(client.options):
120
+ client.transport.record_lost_event(reason, data_category="transaction")
121
+
122
+ # Only one span (the transaction itself) is discarded, since we did not record any spans here.
123
+ client.transport.record_lost_event(reason, data_category="span")
124
+
125
+ return SamplingResult(
126
+ Decision.DROP,
127
+ attributes=attributes,
128
+ trace_state=trace_state,
129
+ )
130
+
131
+
132
+ def sampled_result(span_context, attributes, sample_rate=None, sample_rand=None):
133
+ # type: (SpanContext, Attributes, Optional[float], Optional[Decimal]) -> SamplingResult
134
+ """
135
+ React to a span being sampled and return a sampled SamplingResult.
136
+
137
+ Update the trace_state with the effective sampled, sample_rate and sample_rand,
138
+ and return an OTel SamplingResult with Decision.RECORD_AND_SAMPLE.
139
+
140
+ See for more info about OTel sampling:
141
+ https://opentelemetry-python.readthedocs.io/en/latest/sdk/trace.sampling.html
142
+ """
143
+ trace_state = _update_trace_state(
144
+ span_context, sampled=True, sample_rate=sample_rate, sample_rand=sample_rand
145
+ )
146
+
147
+ return SamplingResult(
148
+ Decision.RECORD_AND_SAMPLE,
149
+ attributes=attributes,
150
+ trace_state=trace_state,
151
+ )
152
+
153
+
154
+ def _update_trace_state(span_context, sampled, sample_rate=None, sample_rand=None):
155
+ # type: (SpanContext, bool, Optional[float], Optional[Decimal]) -> TraceState
156
+ trace_state = span_context.trace_state
157
+
158
+ sampled = "true" if sampled else "false"
159
+ if TRACESTATE_SAMPLED_KEY not in trace_state:
160
+ trace_state = trace_state.add(TRACESTATE_SAMPLED_KEY, sampled)
161
+ elif trace_state.get(TRACESTATE_SAMPLED_KEY) == "deferred":
162
+ trace_state = trace_state.update(TRACESTATE_SAMPLED_KEY, sampled)
163
+
164
+ if sample_rate is not None:
165
+ trace_state = trace_state.update(TRACESTATE_SAMPLE_RATE_KEY, str(sample_rate))
166
+
167
+ if sample_rand is not None:
168
+ trace_state = trace_state.update(
169
+ TRACESTATE_SAMPLE_RAND_KEY, f"{sample_rand:.6f}" # noqa: E231
170
+ )
171
+
172
+ return trace_state
173
+
174
+
175
+ class SentrySampler(Sampler):
176
+ def should_sample(
177
+ self,
178
+ parent_context, # type: Optional[Context]
179
+ trace_id, # type: int
180
+ name, # type: str
181
+ kind=None, # type: Optional[SpanKind]
182
+ attributes=None, # type: Attributes
183
+ links=None, # type: Optional[Sequence[Link]]
184
+ trace_state=None, # type: Optional[TraceState]
185
+ ):
186
+ # type: (...) -> SamplingResult
187
+ client = sentry_sdk.get_client()
188
+
189
+ parent_span_context = trace.get_current_span(parent_context).get_span_context()
190
+
191
+ attributes = attributes or {}
192
+
193
+ # No tracing enabled, thus no sampling
194
+ if not has_tracing_enabled(client.options):
195
+ return dropped_result(parent_span_context, attributes)
196
+
197
+ # parent_span_context.is_valid means this span has a parent, remote or local
198
+ is_root_span = not parent_span_context.is_valid or parent_span_context.is_remote
199
+
200
+ sample_rate = None
201
+
202
+ parent_sampled = get_parent_sampled(parent_span_context, trace_id)
203
+ parent_sample_rate = get_parent_sample_rate(parent_span_context, trace_id)
204
+ parent_sample_rand = get_parent_sample_rand(parent_span_context, trace_id)
205
+
206
+ if parent_sample_rand is not None:
207
+ # We have a sample_rand on the incoming trace or we already backfilled
208
+ # it in PropagationContext
209
+ sample_rand = parent_sample_rand
210
+ else:
211
+ # We are the head SDK and we need to generate a new sample_rand
212
+ sample_rand = cast(Decimal, _generate_sample_rand(str(trace_id), (0, 1)))
213
+
214
+ # Explicit sampled value provided at start_span
215
+ custom_sampled = cast(
216
+ "Optional[bool]", attributes.get(SentrySpanAttribute.CUSTOM_SAMPLED)
217
+ )
218
+ if custom_sampled is not None:
219
+ if is_root_span:
220
+ sample_rate = float(custom_sampled)
221
+ if sample_rate > 0:
222
+ return sampled_result(
223
+ parent_span_context,
224
+ attributes,
225
+ sample_rate=sample_rate,
226
+ sample_rand=sample_rand,
227
+ )
228
+ else:
229
+ return dropped_result(
230
+ parent_span_context,
231
+ attributes,
232
+ sample_rate=sample_rate,
233
+ sample_rand=sample_rand,
234
+ )
235
+ else:
236
+ logger.debug(
237
+ f"[Tracing.Sampler] Ignoring sampled param for non-root span {name}"
238
+ )
239
+
240
+ # Check if there is a traces_sampler
241
+ # Traces_sampler is responsible to check parent sampled to have full transactions.
242
+ has_traces_sampler = callable(client.options.get("traces_sampler"))
243
+
244
+ sample_rate_to_propagate = None
245
+
246
+ if is_root_span and has_traces_sampler:
247
+ sampling_context = create_sampling_context(
248
+ name, attributes, parent_span_context, trace_id
249
+ )
250
+ sample_rate = client.options["traces_sampler"](sampling_context)
251
+ sample_rate_to_propagate = sample_rate
252
+ else:
253
+ # Check if there is a parent with a sampling decision
254
+ if parent_sampled is not None:
255
+ sample_rate = bool(parent_sampled)
256
+ sample_rate_to_propagate = (
257
+ parent_sample_rate if parent_sample_rate else sample_rate
258
+ )
259
+ else:
260
+ # Check if there is a traces_sample_rate
261
+ sample_rate = client.options.get("traces_sample_rate")
262
+ sample_rate_to_propagate = sample_rate
263
+
264
+ # If the sample rate is invalid, drop the span
265
+ if not is_valid_sample_rate(sample_rate, source=self.__class__.__name__):
266
+ logger.warning(
267
+ f"[Tracing.Sampler] Discarding {name} because of invalid sample rate."
268
+ )
269
+ return dropped_result(parent_span_context, attributes)
270
+
271
+ # Down-sample in case of back pressure monitor says so
272
+ if is_root_span and client.monitor:
273
+ sample_rate /= 2**client.monitor.downsample_factor
274
+ if client.monitor.downsample_factor > 0:
275
+ sample_rate_to_propagate = sample_rate
276
+
277
+ # Compare sample_rand to sample_rate to make the final sampling decision
278
+ sample_rate = float(cast("Union[bool, float, int]", sample_rate))
279
+ sampled = sample_rand < Decimal.from_float(sample_rate)
280
+
281
+ if sampled:
282
+ if is_root_span:
283
+ logger.debug(
284
+ f"[Tracing.Sampler] Sampled #{name} with sample_rate: {sample_rate} and sample_rand: {sample_rand}"
285
+ )
286
+
287
+ return sampled_result(
288
+ parent_span_context,
289
+ attributes,
290
+ sample_rate=sample_rate_to_propagate,
291
+ sample_rand=None if sample_rand == parent_sample_rand else sample_rand,
292
+ )
293
+ else:
294
+ if is_root_span:
295
+ logger.debug(
296
+ f"[Tracing.Sampler] Dropped #{name} with sample_rate: {sample_rate} and sample_rand: {sample_rand}"
297
+ )
298
+
299
+ return dropped_result(
300
+ parent_span_context,
301
+ attributes,
302
+ sample_rate=sample_rate_to_propagate,
303
+ sample_rand=None if sample_rand == parent_sample_rand else sample_rand,
304
+ )
305
+
306
+ def get_description(self) -> str:
307
+ return self.__class__.__name__
308
+
309
+
310
+ def create_sampling_context(name, attributes, parent_span_context, trace_id):
311
+ # type: (str, Attributes, Optional[SpanContext], int) -> dict[str, Any]
312
+ sampling_context = {
313
+ "transaction_context": {
314
+ "name": name,
315
+ "op": attributes.get(SentrySpanAttribute.OP) if attributes else None,
316
+ "source": (
317
+ attributes.get(SentrySpanAttribute.SOURCE) if attributes else None
318
+ ),
319
+ },
320
+ "parent_sampled": get_parent_sampled(parent_span_context, trace_id),
321
+ } # type: dict[str, Any]
322
+
323
+ if attributes is not None:
324
+ sampling_context.update(attributes)
325
+
326
+ return sampling_context