sentry-sdk 2.26.1__py2.py3-none-any.whl → 3.0.0a1__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 (114) hide show
  1. sentry_sdk/__init__.py +4 -8
  2. sentry_sdk/_compat.py +0 -1
  3. sentry_sdk/_init_implementation.py +6 -44
  4. sentry_sdk/_log_batcher.py +47 -28
  5. sentry_sdk/_types.py +8 -64
  6. sentry_sdk/ai/monitoring.py +14 -10
  7. sentry_sdk/ai/utils.py +1 -1
  8. sentry_sdk/api.py +69 -163
  9. sentry_sdk/client.py +25 -72
  10. sentry_sdk/consts.py +42 -23
  11. sentry_sdk/debug.py +0 -10
  12. sentry_sdk/envelope.py +2 -10
  13. sentry_sdk/feature_flags.py +5 -1
  14. sentry_sdk/integrations/__init__.py +5 -2
  15. sentry_sdk/integrations/_asgi_common.py +3 -3
  16. sentry_sdk/integrations/_wsgi_common.py +11 -40
  17. sentry_sdk/integrations/aiohttp.py +104 -57
  18. sentry_sdk/integrations/anthropic.py +10 -7
  19. sentry_sdk/integrations/arq.py +24 -13
  20. sentry_sdk/integrations/asgi.py +103 -83
  21. sentry_sdk/integrations/asyncio.py +1 -0
  22. sentry_sdk/integrations/asyncpg.py +45 -30
  23. sentry_sdk/integrations/aws_lambda.py +109 -92
  24. sentry_sdk/integrations/boto3.py +38 -9
  25. sentry_sdk/integrations/bottle.py +1 -1
  26. sentry_sdk/integrations/celery/__init__.py +48 -38
  27. sentry_sdk/integrations/clickhouse_driver.py +59 -28
  28. sentry_sdk/integrations/cohere.py +2 -0
  29. sentry_sdk/integrations/django/__init__.py +25 -46
  30. sentry_sdk/integrations/django/asgi.py +6 -2
  31. sentry_sdk/integrations/django/caching.py +13 -22
  32. sentry_sdk/integrations/django/middleware.py +1 -0
  33. sentry_sdk/integrations/django/signals_handlers.py +3 -1
  34. sentry_sdk/integrations/django/templates.py +8 -12
  35. sentry_sdk/integrations/django/transactions.py +1 -6
  36. sentry_sdk/integrations/django/views.py +5 -2
  37. sentry_sdk/integrations/falcon.py +7 -25
  38. sentry_sdk/integrations/fastapi.py +3 -3
  39. sentry_sdk/integrations/flask.py +1 -1
  40. sentry_sdk/integrations/gcp.py +63 -38
  41. sentry_sdk/integrations/graphene.py +6 -13
  42. sentry_sdk/integrations/grpc/aio/client.py +14 -8
  43. sentry_sdk/integrations/grpc/aio/server.py +19 -21
  44. sentry_sdk/integrations/grpc/client.py +8 -6
  45. sentry_sdk/integrations/grpc/server.py +12 -14
  46. sentry_sdk/integrations/httpx.py +47 -12
  47. sentry_sdk/integrations/huey.py +26 -22
  48. sentry_sdk/integrations/huggingface_hub.py +1 -0
  49. sentry_sdk/integrations/langchain.py +22 -15
  50. sentry_sdk/integrations/launchdarkly.py +3 -3
  51. sentry_sdk/integrations/litestar.py +4 -2
  52. sentry_sdk/integrations/logging.py +12 -3
  53. sentry_sdk/integrations/openai.py +2 -0
  54. sentry_sdk/integrations/openfeature.py +3 -5
  55. sentry_sdk/integrations/pymongo.py +18 -25
  56. sentry_sdk/integrations/pyramid.py +1 -1
  57. sentry_sdk/integrations/quart.py +3 -3
  58. sentry_sdk/integrations/ray.py +23 -17
  59. sentry_sdk/integrations/redis/_async_common.py +30 -18
  60. sentry_sdk/integrations/redis/_sync_common.py +28 -18
  61. sentry_sdk/integrations/redis/modules/caches.py +13 -10
  62. sentry_sdk/integrations/redis/modules/queries.py +14 -11
  63. sentry_sdk/integrations/redis/rb.py +4 -4
  64. sentry_sdk/integrations/redis/redis.py +6 -6
  65. sentry_sdk/integrations/redis/redis_cluster.py +18 -16
  66. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +4 -4
  67. sentry_sdk/integrations/redis/utils.py +63 -19
  68. sentry_sdk/integrations/rq.py +68 -23
  69. sentry_sdk/integrations/rust_tracing.py +28 -43
  70. sentry_sdk/integrations/sanic.py +23 -13
  71. sentry_sdk/integrations/socket.py +9 -5
  72. sentry_sdk/integrations/sqlalchemy.py +8 -8
  73. sentry_sdk/integrations/starlette.py +11 -31
  74. sentry_sdk/integrations/starlite.py +4 -2
  75. sentry_sdk/integrations/stdlib.py +56 -9
  76. sentry_sdk/integrations/strawberry.py +40 -59
  77. sentry_sdk/integrations/threading.py +10 -26
  78. sentry_sdk/integrations/tornado.py +57 -18
  79. sentry_sdk/integrations/trytond.py +4 -1
  80. sentry_sdk/integrations/unleash.py +2 -3
  81. sentry_sdk/integrations/wsgi.py +84 -38
  82. sentry_sdk/opentelemetry/__init__.py +9 -0
  83. sentry_sdk/opentelemetry/consts.py +33 -0
  84. sentry_sdk/opentelemetry/contextvars_context.py +73 -0
  85. sentry_sdk/{integrations/opentelemetry → opentelemetry}/propagator.py +19 -28
  86. sentry_sdk/opentelemetry/sampler.py +326 -0
  87. sentry_sdk/opentelemetry/scope.py +218 -0
  88. sentry_sdk/opentelemetry/span_processor.py +329 -0
  89. sentry_sdk/opentelemetry/tracing.py +35 -0
  90. sentry_sdk/opentelemetry/utils.py +476 -0
  91. sentry_sdk/profiler/__init__.py +0 -40
  92. sentry_sdk/profiler/continuous_profiler.py +1 -30
  93. sentry_sdk/profiler/transaction_profiler.py +5 -56
  94. sentry_sdk/scope.py +107 -351
  95. sentry_sdk/sessions.py +0 -87
  96. sentry_sdk/tracing.py +418 -1134
  97. sentry_sdk/tracing_utils.py +134 -169
  98. sentry_sdk/transport.py +4 -104
  99. sentry_sdk/types.py +26 -2
  100. sentry_sdk/utils.py +169 -152
  101. {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/METADATA +3 -5
  102. sentry_sdk-3.0.0a1.dist-info/RECORD +154 -0
  103. {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/WHEEL +1 -1
  104. sentry_sdk-3.0.0a1.dist-info/entry_points.txt +2 -0
  105. sentry_sdk/hub.py +0 -739
  106. sentry_sdk/integrations/opentelemetry/__init__.py +0 -7
  107. sentry_sdk/integrations/opentelemetry/consts.py +0 -5
  108. sentry_sdk/integrations/opentelemetry/integration.py +0 -58
  109. sentry_sdk/integrations/opentelemetry/span_processor.py +0 -391
  110. sentry_sdk/metrics.py +0 -965
  111. sentry_sdk-2.26.1.dist-info/RECORD +0 -152
  112. sentry_sdk-2.26.1.dist-info/entry_points.txt +0 -2
  113. {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/licenses/LICENSE +0 -0
  114. {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,329 @@
1
+ from collections import deque, defaultdict
2
+ from typing import cast
3
+
4
+ from opentelemetry.trace import (
5
+ format_trace_id,
6
+ format_span_id,
7
+ get_current_span,
8
+ INVALID_SPAN,
9
+ Span as AbstractSpan,
10
+ )
11
+ from opentelemetry.context import Context
12
+ from opentelemetry.sdk.trace import Span, ReadableSpan, SpanProcessor
13
+
14
+ import sentry_sdk
15
+ from sentry_sdk.consts import SPANDATA, DEFAULT_SPAN_ORIGIN
16
+ from sentry_sdk.utils import get_current_thread_meta
17
+ from sentry_sdk.opentelemetry.consts import (
18
+ OTEL_SENTRY_CONTEXT,
19
+ SentrySpanAttribute,
20
+ )
21
+ from sentry_sdk.opentelemetry.sampler import create_sampling_context
22
+ from sentry_sdk.opentelemetry.utils import (
23
+ is_sentry_span,
24
+ convert_from_otel_timestamp,
25
+ extract_span_attributes,
26
+ extract_span_data,
27
+ extract_transaction_name_source,
28
+ get_trace_context,
29
+ get_profile_context,
30
+ get_sentry_meta,
31
+ set_sentry_meta,
32
+ )
33
+ from sentry_sdk.profiler.continuous_profiler import (
34
+ try_autostart_continuous_profiler,
35
+ get_profiler_id,
36
+ try_profile_lifecycle_trace_start,
37
+ )
38
+ from sentry_sdk.profiler.transaction_profiler import Profile
39
+ from sentry_sdk._types import TYPE_CHECKING
40
+
41
+ if TYPE_CHECKING:
42
+ from typing import Optional, List, Any, Deque, DefaultDict
43
+ from sentry_sdk._types import Event
44
+
45
+
46
+ DEFAULT_MAX_SPANS = 1000
47
+
48
+
49
+ class SentrySpanProcessor(SpanProcessor):
50
+ """
51
+ Converts OTel spans into Sentry spans so they can be sent to the Sentry backend.
52
+ """
53
+
54
+ def __new__(cls):
55
+ # type: () -> SentrySpanProcessor
56
+ if not hasattr(cls, "instance"):
57
+ cls.instance = super().__new__(cls)
58
+
59
+ return cls.instance
60
+
61
+ def __init__(self):
62
+ # type: () -> None
63
+ self._children_spans = defaultdict(
64
+ list
65
+ ) # type: DefaultDict[int, List[ReadableSpan]]
66
+ self._dropped_spans = defaultdict(lambda: 0) # type: DefaultDict[int, int]
67
+
68
+ def on_start(self, span, parent_context=None):
69
+ # type: (Span, Optional[Context]) -> None
70
+ if is_sentry_span(span):
71
+ return
72
+
73
+ self._add_root_span(span, get_current_span(parent_context))
74
+ self._start_profile(span)
75
+
76
+ def on_end(self, span):
77
+ # type: (ReadableSpan) -> None
78
+ if is_sentry_span(span):
79
+ return
80
+
81
+ is_root_span = not span.parent or span.parent.is_remote
82
+ if is_root_span:
83
+ # if have a root span ending, stop the profiler, build a transaction and send it
84
+ self._stop_profile(span)
85
+ self._flush_root_span(span)
86
+ else:
87
+ self._append_child_span(span)
88
+
89
+ # TODO-neel-potel not sure we need a clear like JS
90
+ def shutdown(self):
91
+ # type: () -> None
92
+ pass
93
+
94
+ # TODO-neel-potel change default? this is 30 sec
95
+ # TODO-neel-potel call this in client.flush
96
+ def force_flush(self, timeout_millis=30000):
97
+ # type: (int) -> bool
98
+ return True
99
+
100
+ def _add_root_span(self, span, parent_span):
101
+ # type: (Span, AbstractSpan) -> None
102
+ """
103
+ This is required to make Span.root_span work
104
+ since we can't traverse back to the root purely with otel efficiently.
105
+ """
106
+ if parent_span != INVALID_SPAN and not parent_span.get_span_context().is_remote:
107
+ # child span points to parent's root or parent
108
+ parent_root_span = get_sentry_meta(parent_span, "root_span")
109
+ set_sentry_meta(span, "root_span", parent_root_span or parent_span)
110
+ else:
111
+ # root span points to itself
112
+ set_sentry_meta(span, "root_span", span)
113
+
114
+ def _start_profile(self, span):
115
+ # type: (Span) -> None
116
+ try_autostart_continuous_profiler()
117
+
118
+ profiler_id = get_profiler_id()
119
+ thread_id, thread_name = get_current_thread_meta()
120
+
121
+ if profiler_id:
122
+ span.set_attribute(SPANDATA.PROFILER_ID, profiler_id)
123
+ if thread_id:
124
+ span.set_attribute(SPANDATA.THREAD_ID, str(thread_id))
125
+ if thread_name:
126
+ span.set_attribute(SPANDATA.THREAD_NAME, thread_name)
127
+
128
+ is_root_span = not span.parent or span.parent.is_remote
129
+ sampled = span.context and span.context.trace_flags.sampled
130
+
131
+ if is_root_span and sampled:
132
+ # profiler uses time.perf_counter_ns() so we cannot use the
133
+ # unix timestamp that is on span.start_time
134
+ # setting it to 0 means the profiler will internally measure time on start
135
+ profile = Profile(sampled, 0)
136
+
137
+ sampling_context = create_sampling_context(
138
+ span.name, span.attributes, span.parent, span.context.trace_id
139
+ )
140
+ profile._set_initial_sampling_decision(sampling_context)
141
+ profile.__enter__()
142
+ set_sentry_meta(span, "profile", profile)
143
+
144
+ continuous_profile = try_profile_lifecycle_trace_start()
145
+ profiler_id = get_profiler_id()
146
+ if profiler_id:
147
+ span.set_attribute(SPANDATA.PROFILER_ID, profiler_id)
148
+ set_sentry_meta(span, "continuous_profile", continuous_profile)
149
+
150
+ def _stop_profile(self, span):
151
+ # type: (ReadableSpan) -> None
152
+ continuous_profiler = get_sentry_meta(span, "continuous_profile")
153
+ if continuous_profiler:
154
+ continuous_profiler.stop()
155
+
156
+ def _flush_root_span(self, span):
157
+ # type: (ReadableSpan) -> None
158
+ transaction_event = self._root_span_to_transaction_event(span)
159
+ if not transaction_event:
160
+ return
161
+
162
+ collected_spans, dropped_spans = self._collect_children(span)
163
+ spans = []
164
+ for child in collected_spans:
165
+ span_json = self._span_to_json(child)
166
+ if span_json:
167
+ spans.append(span_json)
168
+
169
+ transaction_event["spans"] = spans
170
+ if dropped_spans > 0:
171
+ transaction_event["_dropped_spans"] = dropped_spans
172
+
173
+ # TODO-neel-potel sort and cutoff max spans
174
+
175
+ sentry_sdk.capture_event(transaction_event)
176
+
177
+ def _append_child_span(self, span):
178
+ # type: (ReadableSpan) -> None
179
+ if not span.parent:
180
+ return
181
+
182
+ max_spans = (
183
+ sentry_sdk.get_client().options["_experiments"].get("max_spans")
184
+ or DEFAULT_MAX_SPANS
185
+ )
186
+
187
+ children_spans = self._children_spans[span.parent.span_id]
188
+ if len(children_spans) < max_spans:
189
+ children_spans.append(span)
190
+ else:
191
+ self._dropped_spans[span.parent.span_id] += 1
192
+
193
+ def _collect_children(self, span):
194
+ # type: (ReadableSpan) -> tuple[List[ReadableSpan], int]
195
+ if not span.context:
196
+ return [], 0
197
+
198
+ children = []
199
+ dropped_spans = 0
200
+ bfs_queue = deque() # type: Deque[int]
201
+ bfs_queue.append(span.context.span_id)
202
+
203
+ while bfs_queue:
204
+ parent_span_id = bfs_queue.popleft()
205
+ node_children = self._children_spans.pop(parent_span_id, [])
206
+ dropped_spans += self._dropped_spans.pop(parent_span_id, 0)
207
+ children.extend(node_children)
208
+ bfs_queue.extend(
209
+ [child.context.span_id for child in node_children if child.context]
210
+ )
211
+
212
+ return children, dropped_spans
213
+
214
+ # we construct the event from scratch here
215
+ # and not use the current Transaction class for easier refactoring
216
+ def _root_span_to_transaction_event(self, span):
217
+ # type: (ReadableSpan) -> Optional[Event]
218
+ if not span.context:
219
+ return None
220
+
221
+ event = self._common_span_transaction_attributes_as_json(span)
222
+ if event is None:
223
+ return None
224
+
225
+ transaction_name, transaction_source = extract_transaction_name_source(span)
226
+ span_data = extract_span_data(span)
227
+ trace_context = get_trace_context(span, span_data=span_data)
228
+ contexts = {"trace": trace_context}
229
+
230
+ profile_context = get_profile_context(span)
231
+ if profile_context:
232
+ contexts["profile"] = profile_context
233
+
234
+ (_, description, _, http_status, _) = span_data
235
+
236
+ if http_status:
237
+ contexts["response"] = {"status_code": http_status}
238
+
239
+ if span.resource.attributes:
240
+ contexts[OTEL_SENTRY_CONTEXT] = {"resource": dict(span.resource.attributes)}
241
+
242
+ event.update(
243
+ {
244
+ "type": "transaction",
245
+ "transaction": transaction_name or description,
246
+ "transaction_info": {"source": transaction_source or "custom"},
247
+ "contexts": contexts,
248
+ }
249
+ )
250
+
251
+ profile = cast("Optional[Profile]", get_sentry_meta(span, "profile"))
252
+ if profile:
253
+ profile.__exit__(None, None, None)
254
+ if profile.valid():
255
+ event["profile"] = profile
256
+ set_sentry_meta(span, "profile", None)
257
+
258
+ return event
259
+
260
+ def _span_to_json(self, span):
261
+ # type: (ReadableSpan) -> Optional[dict[str, Any]]
262
+ if not span.context:
263
+ return None
264
+
265
+ # This is a safe cast because dict[str, Any] is a superset of Event
266
+ span_json = cast(
267
+ "dict[str, Any]", self._common_span_transaction_attributes_as_json(span)
268
+ )
269
+ if span_json is None:
270
+ return None
271
+
272
+ trace_id = format_trace_id(span.context.trace_id)
273
+ span_id = format_span_id(span.context.span_id)
274
+ parent_span_id = format_span_id(span.parent.span_id) if span.parent else None
275
+
276
+ (op, description, status, _, origin) = extract_span_data(span)
277
+
278
+ span_json.update(
279
+ {
280
+ "trace_id": trace_id,
281
+ "span_id": span_id,
282
+ "op": op,
283
+ "description": description,
284
+ "status": status,
285
+ "origin": origin or DEFAULT_SPAN_ORIGIN,
286
+ }
287
+ )
288
+
289
+ if parent_span_id:
290
+ span_json["parent_span_id"] = parent_span_id
291
+
292
+ attributes = getattr(span, "attributes", {}) or {}
293
+ if attributes:
294
+ span_json["data"] = {}
295
+ for key, value in attributes.items():
296
+ if not key.startswith("_"):
297
+ span_json["data"][key] = value
298
+
299
+ return span_json
300
+
301
+ def _common_span_transaction_attributes_as_json(self, span):
302
+ # type: (ReadableSpan) -> Optional[Event]
303
+ if not span.start_time or not span.end_time:
304
+ return None
305
+
306
+ common_json = {
307
+ "start_timestamp": convert_from_otel_timestamp(span.start_time),
308
+ "timestamp": convert_from_otel_timestamp(span.end_time),
309
+ } # type: Event
310
+
311
+ tags = extract_span_attributes(span, SentrySpanAttribute.TAG)
312
+ if tags:
313
+ common_json["tags"] = tags
314
+
315
+ return common_json
316
+
317
+ def _log_debug_info(self):
318
+ # type: () -> None
319
+ import pprint
320
+
321
+ pprint.pprint(
322
+ {
323
+ format_span_id(span_id): [
324
+ (format_span_id(child.context.span_id), child.name)
325
+ for child in children
326
+ ]
327
+ for span_id, children in self._children_spans.items()
328
+ }
329
+ )
@@ -0,0 +1,35 @@
1
+ from opentelemetry import trace
2
+ from opentelemetry.propagate import set_global_textmap
3
+ from opentelemetry.sdk.trace import TracerProvider, Span, ReadableSpan
4
+
5
+ from sentry_sdk.opentelemetry import (
6
+ SentryPropagator,
7
+ SentrySampler,
8
+ SentrySpanProcessor,
9
+ )
10
+
11
+
12
+ def patch_readable_span():
13
+ # type: () -> None
14
+ """
15
+ We need to pass through sentry specific metadata/objects from Span to ReadableSpan
16
+ to work with them consistently in the SpanProcessor.
17
+ """
18
+ old_readable_span = Span._readable_span
19
+
20
+ def sentry_patched_readable_span(self):
21
+ # type: (Span) -> ReadableSpan
22
+ readable_span = old_readable_span(self)
23
+ readable_span._sentry_meta = getattr(self, "_sentry_meta", {}) # type: ignore[attr-defined]
24
+ return readable_span
25
+
26
+ Span._readable_span = sentry_patched_readable_span # type: ignore[method-assign]
27
+
28
+
29
+ def setup_sentry_tracing():
30
+ # type: () -> None
31
+ provider = TracerProvider(sampler=SentrySampler())
32
+ provider.add_span_processor(SentrySpanProcessor())
33
+ trace.set_tracer_provider(provider)
34
+
35
+ set_global_textmap(SentryPropagator())