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.
- sentry_sdk/__init__.py +4 -8
- sentry_sdk/_compat.py +0 -1
- sentry_sdk/_init_implementation.py +6 -44
- sentry_sdk/_log_batcher.py +47 -28
- sentry_sdk/_types.py +8 -64
- sentry_sdk/ai/monitoring.py +14 -10
- sentry_sdk/ai/utils.py +1 -1
- sentry_sdk/api.py +69 -163
- sentry_sdk/client.py +25 -72
- sentry_sdk/consts.py +42 -23
- sentry_sdk/debug.py +0 -10
- sentry_sdk/envelope.py +2 -10
- sentry_sdk/feature_flags.py +5 -1
- sentry_sdk/integrations/__init__.py +5 -2
- sentry_sdk/integrations/_asgi_common.py +3 -3
- sentry_sdk/integrations/_wsgi_common.py +11 -40
- sentry_sdk/integrations/aiohttp.py +104 -57
- sentry_sdk/integrations/anthropic.py +10 -7
- sentry_sdk/integrations/arq.py +24 -13
- sentry_sdk/integrations/asgi.py +103 -83
- sentry_sdk/integrations/asyncio.py +1 -0
- sentry_sdk/integrations/asyncpg.py +45 -30
- sentry_sdk/integrations/aws_lambda.py +109 -92
- sentry_sdk/integrations/boto3.py +38 -9
- sentry_sdk/integrations/bottle.py +1 -1
- sentry_sdk/integrations/celery/__init__.py +48 -38
- sentry_sdk/integrations/clickhouse_driver.py +59 -28
- sentry_sdk/integrations/cohere.py +2 -0
- sentry_sdk/integrations/django/__init__.py +25 -46
- sentry_sdk/integrations/django/asgi.py +6 -2
- sentry_sdk/integrations/django/caching.py +13 -22
- sentry_sdk/integrations/django/middleware.py +1 -0
- sentry_sdk/integrations/django/signals_handlers.py +3 -1
- sentry_sdk/integrations/django/templates.py +8 -12
- sentry_sdk/integrations/django/transactions.py +1 -6
- sentry_sdk/integrations/django/views.py +5 -2
- sentry_sdk/integrations/falcon.py +7 -25
- sentry_sdk/integrations/fastapi.py +3 -3
- sentry_sdk/integrations/flask.py +1 -1
- sentry_sdk/integrations/gcp.py +63 -38
- sentry_sdk/integrations/graphene.py +6 -13
- sentry_sdk/integrations/grpc/aio/client.py +14 -8
- sentry_sdk/integrations/grpc/aio/server.py +19 -21
- sentry_sdk/integrations/grpc/client.py +8 -6
- sentry_sdk/integrations/grpc/server.py +12 -14
- sentry_sdk/integrations/httpx.py +47 -12
- sentry_sdk/integrations/huey.py +26 -22
- sentry_sdk/integrations/huggingface_hub.py +1 -0
- sentry_sdk/integrations/langchain.py +22 -15
- sentry_sdk/integrations/launchdarkly.py +3 -3
- sentry_sdk/integrations/litestar.py +4 -2
- sentry_sdk/integrations/logging.py +12 -3
- sentry_sdk/integrations/openai.py +2 -0
- sentry_sdk/integrations/openfeature.py +3 -5
- sentry_sdk/integrations/pymongo.py +18 -25
- sentry_sdk/integrations/pyramid.py +1 -1
- sentry_sdk/integrations/quart.py +3 -3
- sentry_sdk/integrations/ray.py +23 -17
- sentry_sdk/integrations/redis/_async_common.py +30 -18
- sentry_sdk/integrations/redis/_sync_common.py +28 -18
- sentry_sdk/integrations/redis/modules/caches.py +13 -10
- sentry_sdk/integrations/redis/modules/queries.py +14 -11
- sentry_sdk/integrations/redis/rb.py +4 -4
- sentry_sdk/integrations/redis/redis.py +6 -6
- sentry_sdk/integrations/redis/redis_cluster.py +18 -16
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +4 -4
- sentry_sdk/integrations/redis/utils.py +63 -19
- sentry_sdk/integrations/rq.py +68 -23
- sentry_sdk/integrations/rust_tracing.py +28 -43
- sentry_sdk/integrations/sanic.py +23 -13
- sentry_sdk/integrations/socket.py +9 -5
- sentry_sdk/integrations/sqlalchemy.py +8 -8
- sentry_sdk/integrations/starlette.py +11 -31
- sentry_sdk/integrations/starlite.py +4 -2
- sentry_sdk/integrations/stdlib.py +56 -9
- sentry_sdk/integrations/strawberry.py +40 -59
- sentry_sdk/integrations/threading.py +10 -26
- sentry_sdk/integrations/tornado.py +57 -18
- sentry_sdk/integrations/trytond.py +4 -1
- sentry_sdk/integrations/unleash.py +2 -3
- sentry_sdk/integrations/wsgi.py +84 -38
- sentry_sdk/opentelemetry/__init__.py +9 -0
- sentry_sdk/opentelemetry/consts.py +33 -0
- sentry_sdk/opentelemetry/contextvars_context.py +73 -0
- sentry_sdk/{integrations/opentelemetry → opentelemetry}/propagator.py +19 -28
- sentry_sdk/opentelemetry/sampler.py +326 -0
- sentry_sdk/opentelemetry/scope.py +218 -0
- sentry_sdk/opentelemetry/span_processor.py +329 -0
- sentry_sdk/opentelemetry/tracing.py +35 -0
- sentry_sdk/opentelemetry/utils.py +476 -0
- sentry_sdk/profiler/__init__.py +0 -40
- sentry_sdk/profiler/continuous_profiler.py +1 -30
- sentry_sdk/profiler/transaction_profiler.py +5 -56
- sentry_sdk/scope.py +107 -351
- sentry_sdk/sessions.py +0 -87
- sentry_sdk/tracing.py +418 -1134
- sentry_sdk/tracing_utils.py +134 -169
- sentry_sdk/transport.py +4 -104
- sentry_sdk/types.py +26 -2
- sentry_sdk/utils.py +169 -152
- {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/METADATA +3 -5
- sentry_sdk-3.0.0a1.dist-info/RECORD +154 -0
- {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/WHEEL +1 -1
- sentry_sdk-3.0.0a1.dist-info/entry_points.txt +2 -0
- sentry_sdk/hub.py +0 -739
- sentry_sdk/integrations/opentelemetry/__init__.py +0 -7
- sentry_sdk/integrations/opentelemetry/consts.py +0 -5
- sentry_sdk/integrations/opentelemetry/integration.py +0 -58
- sentry_sdk/integrations/opentelemetry/span_processor.py +0 -391
- sentry_sdk/metrics.py +0 -965
- sentry_sdk-2.26.1.dist-info/RECORD +0 -152
- sentry_sdk-2.26.1.dist-info/entry_points.txt +0 -2
- {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/licenses/LICENSE +0 -0
- {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())
|