sentry-sdk 2.27.0__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 +2 -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 +2 -2
- sentry_sdk/integrations/__init__.py +4 -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 +102 -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/litestar.py +4 -2
- sentry_sdk/integrations/logging.py +12 -3
- sentry_sdk/integrations/openai.py +2 -0
- 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/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 -1144
- sentry_sdk/tracing_utils.py +126 -164
- sentry_sdk/transport.py +4 -104
- sentry_sdk/utils.py +169 -152
- {sentry_sdk-2.27.0.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.27.0.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.27.0.dist-info/RECORD +0 -152
- sentry_sdk-2.27.0.dist-info/entry_points.txt +0 -2
- {sentry_sdk-2.27.0.dist-info → sentry_sdk-3.0.0a1.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-2.27.0.dist-info → sentry_sdk-3.0.0a1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import cast
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
from urllib3.util import parse_url as urlparse
|
|
6
|
+
from urllib.parse import quote, unquote
|
|
7
|
+
from opentelemetry.trace import (
|
|
8
|
+
Span as AbstractSpan,
|
|
9
|
+
SpanKind,
|
|
10
|
+
StatusCode,
|
|
11
|
+
format_trace_id,
|
|
12
|
+
format_span_id,
|
|
13
|
+
TraceState,
|
|
14
|
+
)
|
|
15
|
+
from opentelemetry.semconv.trace import SpanAttributes
|
|
16
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
17
|
+
|
|
18
|
+
import sentry_sdk
|
|
19
|
+
from sentry_sdk.utils import Dsn
|
|
20
|
+
from sentry_sdk.consts import (
|
|
21
|
+
SPANSTATUS,
|
|
22
|
+
OP,
|
|
23
|
+
SPANDATA,
|
|
24
|
+
DEFAULT_SPAN_ORIGIN,
|
|
25
|
+
LOW_QUALITY_TRANSACTION_SOURCES,
|
|
26
|
+
)
|
|
27
|
+
from sentry_sdk.opentelemetry.consts import SentrySpanAttribute
|
|
28
|
+
from sentry_sdk.tracing_utils import Baggage, get_span_status_from_http_code
|
|
29
|
+
|
|
30
|
+
from sentry_sdk._types import TYPE_CHECKING
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from typing import Any, Optional, Mapping, Sequence, Union
|
|
34
|
+
from sentry_sdk._types import OtelExtractedSpanData
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
GRPC_ERROR_MAP = {
|
|
38
|
+
"1": SPANSTATUS.CANCELLED,
|
|
39
|
+
"2": SPANSTATUS.UNKNOWN_ERROR,
|
|
40
|
+
"3": SPANSTATUS.INVALID_ARGUMENT,
|
|
41
|
+
"4": SPANSTATUS.DEADLINE_EXCEEDED,
|
|
42
|
+
"5": SPANSTATUS.NOT_FOUND,
|
|
43
|
+
"6": SPANSTATUS.ALREADY_EXISTS,
|
|
44
|
+
"7": SPANSTATUS.PERMISSION_DENIED,
|
|
45
|
+
"8": SPANSTATUS.RESOURCE_EXHAUSTED,
|
|
46
|
+
"9": SPANSTATUS.FAILED_PRECONDITION,
|
|
47
|
+
"10": SPANSTATUS.ABORTED,
|
|
48
|
+
"11": SPANSTATUS.OUT_OF_RANGE,
|
|
49
|
+
"12": SPANSTATUS.UNIMPLEMENTED,
|
|
50
|
+
"13": SPANSTATUS.INTERNAL_ERROR,
|
|
51
|
+
"14": SPANSTATUS.UNAVAILABLE,
|
|
52
|
+
"15": SPANSTATUS.DATA_LOSS,
|
|
53
|
+
"16": SPANSTATUS.UNAUTHENTICATED,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def is_sentry_span(span):
|
|
58
|
+
# type: (ReadableSpan) -> bool
|
|
59
|
+
"""
|
|
60
|
+
Break infinite loop:
|
|
61
|
+
HTTP requests to Sentry are caught by OTel and send again to Sentry.
|
|
62
|
+
"""
|
|
63
|
+
from sentry_sdk import get_client
|
|
64
|
+
|
|
65
|
+
if not span.attributes:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
span_url = span.attributes.get(SpanAttributes.HTTP_URL, None)
|
|
69
|
+
span_url = cast("Optional[str]", span_url)
|
|
70
|
+
|
|
71
|
+
if not span_url:
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
dsn_url = None
|
|
75
|
+
client = get_client()
|
|
76
|
+
|
|
77
|
+
if client.dsn:
|
|
78
|
+
try:
|
|
79
|
+
dsn_url = Dsn(client.dsn).netloc
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
if not dsn_url:
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
if dsn_url in span_url:
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def convert_from_otel_timestamp(time):
|
|
93
|
+
# type: (int) -> datetime
|
|
94
|
+
"""Convert an OTel nanosecond-level timestamp to a datetime."""
|
|
95
|
+
return datetime.fromtimestamp(time / 1e9, timezone.utc)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def convert_to_otel_timestamp(time):
|
|
99
|
+
# type: (Union[datetime, float]) -> int
|
|
100
|
+
"""Convert a datetime to an OTel timestamp (with nanosecond precision)."""
|
|
101
|
+
if isinstance(time, datetime):
|
|
102
|
+
return int(time.timestamp() * 1e9)
|
|
103
|
+
return int(time * 1e9)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def extract_transaction_name_source(span):
|
|
107
|
+
# type: (ReadableSpan) -> tuple[Optional[str], Optional[str]]
|
|
108
|
+
if not span.attributes:
|
|
109
|
+
return (None, None)
|
|
110
|
+
return (
|
|
111
|
+
cast("Optional[str]", span.attributes.get(SentrySpanAttribute.NAME)),
|
|
112
|
+
cast("Optional[str]", span.attributes.get(SentrySpanAttribute.SOURCE)),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def extract_span_data(span):
|
|
117
|
+
# type: (ReadableSpan) -> OtelExtractedSpanData
|
|
118
|
+
op = span.name
|
|
119
|
+
description = span.name
|
|
120
|
+
status, http_status = extract_span_status(span)
|
|
121
|
+
origin = None
|
|
122
|
+
if span.attributes is None:
|
|
123
|
+
return (op, description, status, http_status, origin)
|
|
124
|
+
|
|
125
|
+
attribute_op = cast("Optional[str]", span.attributes.get(SentrySpanAttribute.OP))
|
|
126
|
+
op = attribute_op or op
|
|
127
|
+
description = cast(
|
|
128
|
+
"str", span.attributes.get(SentrySpanAttribute.DESCRIPTION) or description
|
|
129
|
+
)
|
|
130
|
+
origin = cast("Optional[str]", span.attributes.get(SentrySpanAttribute.ORIGIN))
|
|
131
|
+
|
|
132
|
+
http_method = span.attributes.get(SpanAttributes.HTTP_METHOD)
|
|
133
|
+
http_method = cast("Optional[str]", http_method)
|
|
134
|
+
if http_method:
|
|
135
|
+
return span_data_for_http_method(span)
|
|
136
|
+
|
|
137
|
+
db_query = span.attributes.get(SpanAttributes.DB_SYSTEM)
|
|
138
|
+
if db_query:
|
|
139
|
+
return span_data_for_db_query(span)
|
|
140
|
+
|
|
141
|
+
rpc_service = span.attributes.get(SpanAttributes.RPC_SERVICE)
|
|
142
|
+
if rpc_service:
|
|
143
|
+
return (
|
|
144
|
+
attribute_op or "rpc",
|
|
145
|
+
description,
|
|
146
|
+
status,
|
|
147
|
+
http_status,
|
|
148
|
+
origin,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
messaging_system = span.attributes.get(SpanAttributes.MESSAGING_SYSTEM)
|
|
152
|
+
if messaging_system:
|
|
153
|
+
return (
|
|
154
|
+
attribute_op or "message",
|
|
155
|
+
description,
|
|
156
|
+
status,
|
|
157
|
+
http_status,
|
|
158
|
+
origin,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
faas_trigger = span.attributes.get(SpanAttributes.FAAS_TRIGGER)
|
|
162
|
+
if faas_trigger:
|
|
163
|
+
return (str(faas_trigger), description, status, http_status, origin)
|
|
164
|
+
|
|
165
|
+
return (op, description, status, http_status, origin)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def span_data_for_http_method(span):
|
|
169
|
+
# type: (ReadableSpan) -> OtelExtractedSpanData
|
|
170
|
+
span_attributes = span.attributes or {}
|
|
171
|
+
|
|
172
|
+
op = cast("Optional[str]", span_attributes.get(SentrySpanAttribute.OP))
|
|
173
|
+
if op is None:
|
|
174
|
+
op = "http"
|
|
175
|
+
|
|
176
|
+
if span.kind == SpanKind.SERVER:
|
|
177
|
+
op += ".server"
|
|
178
|
+
elif span.kind == SpanKind.CLIENT:
|
|
179
|
+
op += ".client"
|
|
180
|
+
|
|
181
|
+
http_method = span_attributes.get(SpanAttributes.HTTP_METHOD)
|
|
182
|
+
route = span_attributes.get(SpanAttributes.HTTP_ROUTE)
|
|
183
|
+
target = span_attributes.get(SpanAttributes.HTTP_TARGET)
|
|
184
|
+
peer_name = span_attributes.get(SpanAttributes.NET_PEER_NAME)
|
|
185
|
+
|
|
186
|
+
# TODO-neel-potel remove description completely
|
|
187
|
+
description = span_attributes.get(
|
|
188
|
+
SentrySpanAttribute.DESCRIPTION
|
|
189
|
+
) or span_attributes.get(SentrySpanAttribute.NAME)
|
|
190
|
+
description = cast("Optional[str]", description)
|
|
191
|
+
if description is None:
|
|
192
|
+
description = f"{http_method}"
|
|
193
|
+
|
|
194
|
+
if route:
|
|
195
|
+
description = f"{http_method} {route}"
|
|
196
|
+
elif target:
|
|
197
|
+
description = f"{http_method} {target}"
|
|
198
|
+
elif peer_name:
|
|
199
|
+
description = f"{http_method} {peer_name}"
|
|
200
|
+
else:
|
|
201
|
+
url = span_attributes.get(SpanAttributes.HTTP_URL)
|
|
202
|
+
url = cast("Optional[str]", url)
|
|
203
|
+
|
|
204
|
+
if url:
|
|
205
|
+
parsed_url = urlparse(url)
|
|
206
|
+
url = "{}://{}{}".format(
|
|
207
|
+
parsed_url.scheme, parsed_url.netloc, parsed_url.path
|
|
208
|
+
)
|
|
209
|
+
description = f"{http_method} {url}"
|
|
210
|
+
|
|
211
|
+
status, http_status = extract_span_status(span)
|
|
212
|
+
|
|
213
|
+
origin = cast("Optional[str]", span_attributes.get(SentrySpanAttribute.ORIGIN))
|
|
214
|
+
|
|
215
|
+
return (op, description, status, http_status, origin)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def span_data_for_db_query(span):
|
|
219
|
+
# type: (ReadableSpan) -> OtelExtractedSpanData
|
|
220
|
+
span_attributes = span.attributes or {}
|
|
221
|
+
|
|
222
|
+
op = cast("str", span_attributes.get(SentrySpanAttribute.OP, OP.DB))
|
|
223
|
+
|
|
224
|
+
statement = span_attributes.get(SpanAttributes.DB_STATEMENT, None)
|
|
225
|
+
statement = cast("Optional[str]", statement)
|
|
226
|
+
|
|
227
|
+
description = statement or span.name
|
|
228
|
+
origin = cast("Optional[str]", span_attributes.get(SentrySpanAttribute.ORIGIN))
|
|
229
|
+
|
|
230
|
+
return (op, description, None, None, origin)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def extract_span_status(span):
|
|
234
|
+
# type: (ReadableSpan) -> tuple[Optional[str], Optional[int]]
|
|
235
|
+
span_attributes = span.attributes or {}
|
|
236
|
+
status = span.status or None
|
|
237
|
+
|
|
238
|
+
if status:
|
|
239
|
+
inferred_status, http_status = infer_status_from_attributes(span_attributes)
|
|
240
|
+
|
|
241
|
+
if status.status_code == StatusCode.OK:
|
|
242
|
+
return (SPANSTATUS.OK, http_status)
|
|
243
|
+
elif status.status_code == StatusCode.ERROR:
|
|
244
|
+
if status.description is None:
|
|
245
|
+
if inferred_status:
|
|
246
|
+
return (inferred_status, http_status)
|
|
247
|
+
|
|
248
|
+
if http_status is not None:
|
|
249
|
+
return (inferred_status, http_status)
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
status.description is not None
|
|
253
|
+
and status.description in GRPC_ERROR_MAP.values()
|
|
254
|
+
):
|
|
255
|
+
return (status.description, None)
|
|
256
|
+
else:
|
|
257
|
+
return (SPANSTATUS.UNKNOWN_ERROR, None)
|
|
258
|
+
|
|
259
|
+
inferred_status, http_status = infer_status_from_attributes(span_attributes)
|
|
260
|
+
if inferred_status:
|
|
261
|
+
return (inferred_status, http_status)
|
|
262
|
+
|
|
263
|
+
if status and status.status_code == StatusCode.UNSET:
|
|
264
|
+
return (None, None)
|
|
265
|
+
else:
|
|
266
|
+
return (SPANSTATUS.UNKNOWN_ERROR, None)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def infer_status_from_attributes(span_attributes):
|
|
270
|
+
# type: (Mapping[str, str | bool | int | float | Sequence[str] | Sequence[bool] | Sequence[int] | Sequence[float]]) -> tuple[Optional[str], Optional[int]]
|
|
271
|
+
http_status = get_http_status_code(span_attributes)
|
|
272
|
+
|
|
273
|
+
if http_status:
|
|
274
|
+
return (get_span_status_from_http_code(http_status), http_status)
|
|
275
|
+
|
|
276
|
+
grpc_status = span_attributes.get(SpanAttributes.RPC_GRPC_STATUS_CODE)
|
|
277
|
+
if grpc_status:
|
|
278
|
+
return (GRPC_ERROR_MAP.get(str(grpc_status), SPANSTATUS.UNKNOWN_ERROR), None)
|
|
279
|
+
|
|
280
|
+
return (None, None)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def get_http_status_code(span_attributes):
|
|
284
|
+
# type: (Mapping[str, str | bool | int | float | Sequence[str] | Sequence[bool] | Sequence[int] | Sequence[float]]) -> Optional[int]
|
|
285
|
+
try:
|
|
286
|
+
http_status = span_attributes.get(SpanAttributes.HTTP_RESPONSE_STATUS_CODE)
|
|
287
|
+
except AttributeError:
|
|
288
|
+
# HTTP_RESPONSE_STATUS_CODE was added in 1.21, so if we're on an older
|
|
289
|
+
# OTel version SpanAttributes.HTTP_RESPONSE_STATUS_CODE will throw an
|
|
290
|
+
# AttributeError
|
|
291
|
+
http_status = None
|
|
292
|
+
|
|
293
|
+
if http_status is None:
|
|
294
|
+
# Fall back to the deprecated attribute
|
|
295
|
+
http_status = span_attributes.get(SpanAttributes.HTTP_STATUS_CODE)
|
|
296
|
+
|
|
297
|
+
http_status = cast("Optional[int]", http_status)
|
|
298
|
+
|
|
299
|
+
return http_status
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def extract_span_attributes(span, namespace):
|
|
303
|
+
# type: (ReadableSpan, str) -> dict[str, Any]
|
|
304
|
+
"""
|
|
305
|
+
Extract Sentry-specific span attributes and make them look the way Sentry expects.
|
|
306
|
+
"""
|
|
307
|
+
extracted_attrs = {} # type: dict[str, Any]
|
|
308
|
+
|
|
309
|
+
for attr, value in (span.attributes or {}).items():
|
|
310
|
+
if attr.startswith(namespace):
|
|
311
|
+
key = attr[len(namespace) + 1 :]
|
|
312
|
+
extracted_attrs[key] = value
|
|
313
|
+
|
|
314
|
+
return extracted_attrs
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def get_trace_context(span, span_data=None):
|
|
318
|
+
# type: (ReadableSpan, Optional[OtelExtractedSpanData]) -> dict[str, Any]
|
|
319
|
+
if not span.context:
|
|
320
|
+
return {}
|
|
321
|
+
|
|
322
|
+
trace_id = format_trace_id(span.context.trace_id)
|
|
323
|
+
span_id = format_span_id(span.context.span_id)
|
|
324
|
+
parent_span_id = format_span_id(span.parent.span_id) if span.parent else None
|
|
325
|
+
|
|
326
|
+
if span_data is None:
|
|
327
|
+
span_data = extract_span_data(span)
|
|
328
|
+
|
|
329
|
+
(op, _, status, _, origin) = span_data
|
|
330
|
+
|
|
331
|
+
trace_context = {
|
|
332
|
+
"trace_id": trace_id,
|
|
333
|
+
"span_id": span_id,
|
|
334
|
+
"parent_span_id": parent_span_id,
|
|
335
|
+
"op": op,
|
|
336
|
+
"origin": origin or DEFAULT_SPAN_ORIGIN,
|
|
337
|
+
} # type: dict[str, Any]
|
|
338
|
+
|
|
339
|
+
if status:
|
|
340
|
+
trace_context["status"] = status
|
|
341
|
+
|
|
342
|
+
if span.attributes:
|
|
343
|
+
trace_context["data"] = dict(span.attributes)
|
|
344
|
+
|
|
345
|
+
trace_state = get_trace_state(span)
|
|
346
|
+
trace_context["dynamic_sampling_context"] = dsc_from_trace_state(trace_state)
|
|
347
|
+
|
|
348
|
+
# TODO-neel-potel profiler thread_id, thread_name
|
|
349
|
+
|
|
350
|
+
return trace_context
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def trace_state_from_baggage(baggage):
|
|
354
|
+
# type: (Baggage) -> TraceState
|
|
355
|
+
items = []
|
|
356
|
+
for k, v in baggage.sentry_items.items():
|
|
357
|
+
key = Baggage.SENTRY_PREFIX + quote(k)
|
|
358
|
+
val = quote(str(v))
|
|
359
|
+
items.append((key, val))
|
|
360
|
+
return TraceState(items)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def baggage_from_trace_state(trace_state):
|
|
364
|
+
# type: (TraceState) -> Baggage
|
|
365
|
+
return Baggage(dsc_from_trace_state(trace_state))
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def serialize_trace_state(trace_state):
|
|
369
|
+
# type: (TraceState) -> str
|
|
370
|
+
sentry_items = []
|
|
371
|
+
for k, v in trace_state.items():
|
|
372
|
+
if Baggage.SENTRY_PREFIX_REGEX.match(k):
|
|
373
|
+
sentry_items.append((k, v))
|
|
374
|
+
return ",".join(key + "=" + value for key, value in sentry_items)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def dsc_from_trace_state(trace_state):
|
|
378
|
+
# type: (TraceState) -> dict[str, str]
|
|
379
|
+
dsc = {}
|
|
380
|
+
for k, v in trace_state.items():
|
|
381
|
+
if Baggage.SENTRY_PREFIX_REGEX.match(k):
|
|
382
|
+
key = re.sub(Baggage.SENTRY_PREFIX_REGEX, "", k)
|
|
383
|
+
dsc[unquote(key)] = unquote(v)
|
|
384
|
+
return dsc
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def has_incoming_trace(trace_state):
|
|
388
|
+
# type: (TraceState) -> bool
|
|
389
|
+
"""
|
|
390
|
+
The existence of a sentry-trace_id in the baggage implies we continued an upstream trace.
|
|
391
|
+
"""
|
|
392
|
+
return (Baggage.SENTRY_PREFIX + "trace_id") in trace_state
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def get_trace_state(span):
|
|
396
|
+
# type: (Union[AbstractSpan, ReadableSpan]) -> TraceState
|
|
397
|
+
"""
|
|
398
|
+
Get the existing trace_state with sentry items
|
|
399
|
+
or populate it if we are the head SDK.
|
|
400
|
+
"""
|
|
401
|
+
span_context = span.get_span_context()
|
|
402
|
+
if not span_context:
|
|
403
|
+
return TraceState()
|
|
404
|
+
|
|
405
|
+
trace_state = span_context.trace_state
|
|
406
|
+
|
|
407
|
+
if has_incoming_trace(trace_state):
|
|
408
|
+
return trace_state
|
|
409
|
+
else:
|
|
410
|
+
client = sentry_sdk.get_client()
|
|
411
|
+
if not client.is_active():
|
|
412
|
+
return trace_state
|
|
413
|
+
|
|
414
|
+
options = client.options or {}
|
|
415
|
+
|
|
416
|
+
trace_state = trace_state.update(
|
|
417
|
+
Baggage.SENTRY_PREFIX + "trace_id",
|
|
418
|
+
quote(format_trace_id(span_context.trace_id)),
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if options.get("environment"):
|
|
422
|
+
trace_state = trace_state.update(
|
|
423
|
+
Baggage.SENTRY_PREFIX + "environment", quote(options["environment"])
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
if options.get("release"):
|
|
427
|
+
trace_state = trace_state.update(
|
|
428
|
+
Baggage.SENTRY_PREFIX + "release", quote(options["release"])
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
if options.get("dsn"):
|
|
432
|
+
trace_state = trace_state.update(
|
|
433
|
+
Baggage.SENTRY_PREFIX + "public_key",
|
|
434
|
+
quote(Dsn(options["dsn"]).public_key),
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
root_span = get_sentry_meta(span, "root_span")
|
|
438
|
+
if root_span and isinstance(root_span, ReadableSpan):
|
|
439
|
+
transaction_name, transaction_source = extract_transaction_name_source(
|
|
440
|
+
root_span
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
if (
|
|
444
|
+
transaction_name
|
|
445
|
+
and transaction_source not in LOW_QUALITY_TRANSACTION_SOURCES
|
|
446
|
+
):
|
|
447
|
+
trace_state = trace_state.update(
|
|
448
|
+
Baggage.SENTRY_PREFIX + "transaction", quote(transaction_name)
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
return trace_state
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def get_sentry_meta(span, key):
|
|
455
|
+
# type: (Union[AbstractSpan, ReadableSpan], str) -> Any
|
|
456
|
+
sentry_meta = getattr(span, "_sentry_meta", None)
|
|
457
|
+
return sentry_meta.get(key) if sentry_meta else None
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def set_sentry_meta(span, key, value):
|
|
461
|
+
# type: (Union[AbstractSpan, ReadableSpan], str, Any) -> None
|
|
462
|
+
sentry_meta = getattr(span, "_sentry_meta", {})
|
|
463
|
+
sentry_meta[key] = value
|
|
464
|
+
span._sentry_meta = sentry_meta # type: ignore[union-attr]
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def get_profile_context(span):
|
|
468
|
+
# type: (ReadableSpan) -> Optional[dict[str, str]]
|
|
469
|
+
if not span.attributes:
|
|
470
|
+
return None
|
|
471
|
+
|
|
472
|
+
profiler_id = cast("Optional[str]", span.attributes.get(SPANDATA.PROFILER_ID))
|
|
473
|
+
if profiler_id is None:
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
return {"profiler_id": profiler_id}
|
sentry_sdk/profiler/__init__.py
CHANGED
|
@@ -1,49 +1,9 @@
|
|
|
1
1
|
from sentry_sdk.profiler.continuous_profiler import (
|
|
2
|
-
start_profile_session,
|
|
3
2
|
start_profiler,
|
|
4
|
-
stop_profile_session,
|
|
5
3
|
stop_profiler,
|
|
6
4
|
)
|
|
7
|
-
from sentry_sdk.profiler.transaction_profiler import (
|
|
8
|
-
MAX_PROFILE_DURATION_NS,
|
|
9
|
-
PROFILE_MINIMUM_SAMPLES,
|
|
10
|
-
Profile,
|
|
11
|
-
Scheduler,
|
|
12
|
-
ThreadScheduler,
|
|
13
|
-
GeventScheduler,
|
|
14
|
-
has_profiling_enabled,
|
|
15
|
-
setup_profiler,
|
|
16
|
-
teardown_profiler,
|
|
17
|
-
)
|
|
18
|
-
from sentry_sdk.profiler.utils import (
|
|
19
|
-
DEFAULT_SAMPLING_FREQUENCY,
|
|
20
|
-
MAX_STACK_DEPTH,
|
|
21
|
-
get_frame_name,
|
|
22
|
-
extract_frame,
|
|
23
|
-
extract_stack,
|
|
24
|
-
frame_id,
|
|
25
|
-
)
|
|
26
5
|
|
|
27
6
|
__all__ = [
|
|
28
|
-
"start_profile_session", # TODO: Deprecate this in favor of `start_profiler`
|
|
29
7
|
"start_profiler",
|
|
30
|
-
"stop_profile_session", # TODO: Deprecate this in favor of `stop_profiler`
|
|
31
8
|
"stop_profiler",
|
|
32
|
-
# DEPRECATED: The following was re-exported for backwards compatibility. It
|
|
33
|
-
# will be removed from sentry_sdk.profiler in a future release.
|
|
34
|
-
"MAX_PROFILE_DURATION_NS",
|
|
35
|
-
"PROFILE_MINIMUM_SAMPLES",
|
|
36
|
-
"Profile",
|
|
37
|
-
"Scheduler",
|
|
38
|
-
"ThreadScheduler",
|
|
39
|
-
"GeventScheduler",
|
|
40
|
-
"has_profiling_enabled",
|
|
41
|
-
"setup_profiler",
|
|
42
|
-
"teardown_profiler",
|
|
43
|
-
"DEFAULT_SAMPLING_FREQUENCY",
|
|
44
|
-
"MAX_STACK_DEPTH",
|
|
45
|
-
"get_frame_name",
|
|
46
|
-
"extract_frame",
|
|
47
|
-
"extract_stack",
|
|
48
|
-
"frame_id",
|
|
49
9
|
]
|
|
@@ -5,7 +5,6 @@ import sys
|
|
|
5
5
|
import threading
|
|
6
6
|
import time
|
|
7
7
|
import uuid
|
|
8
|
-
import warnings
|
|
9
8
|
from collections import deque
|
|
10
9
|
from datetime import datetime, timezone
|
|
11
10
|
|
|
@@ -88,15 +87,9 @@ def setup_continuous_profiler(options, sdk_info, capture_func):
|
|
|
88
87
|
else:
|
|
89
88
|
default_profiler_mode = ThreadContinuousScheduler.mode
|
|
90
89
|
|
|
90
|
+
profiler_mode = default_profiler_mode
|
|
91
91
|
if options.get("profiler_mode") is not None:
|
|
92
92
|
profiler_mode = options["profiler_mode"]
|
|
93
|
-
else:
|
|
94
|
-
# TODO: deprecate this and just use the existing `profiler_mode`
|
|
95
|
-
experiments = options.get("_experiments", {})
|
|
96
|
-
|
|
97
|
-
profiler_mode = (
|
|
98
|
-
experiments.get("continuous_profiling_mode") or default_profiler_mode
|
|
99
|
-
)
|
|
100
93
|
|
|
101
94
|
frequency = DEFAULT_SAMPLING_FREQUENCY
|
|
102
95
|
|
|
@@ -152,17 +145,6 @@ def start_profiler():
|
|
|
152
145
|
_scheduler.manual_start()
|
|
153
146
|
|
|
154
147
|
|
|
155
|
-
def start_profile_session():
|
|
156
|
-
# type: () -> None
|
|
157
|
-
|
|
158
|
-
warnings.warn(
|
|
159
|
-
"The `start_profile_session` function is deprecated. Please use `start_profile` instead.",
|
|
160
|
-
DeprecationWarning,
|
|
161
|
-
stacklevel=2,
|
|
162
|
-
)
|
|
163
|
-
start_profiler()
|
|
164
|
-
|
|
165
|
-
|
|
166
148
|
def stop_profiler():
|
|
167
149
|
# type: () -> None
|
|
168
150
|
if _scheduler is None:
|
|
@@ -171,17 +153,6 @@ def stop_profiler():
|
|
|
171
153
|
_scheduler.manual_stop()
|
|
172
154
|
|
|
173
155
|
|
|
174
|
-
def stop_profile_session():
|
|
175
|
-
# type: () -> None
|
|
176
|
-
|
|
177
|
-
warnings.warn(
|
|
178
|
-
"The `stop_profile_session` function is deprecated. Please use `stop_profile` instead.",
|
|
179
|
-
DeprecationWarning,
|
|
180
|
-
stacklevel=2,
|
|
181
|
-
)
|
|
182
|
-
stop_profiler()
|
|
183
|
-
|
|
184
|
-
|
|
185
156
|
def teardown_continuous_profiler():
|
|
186
157
|
# type: () -> None
|
|
187
158
|
stop_profiler()
|