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
sentry_sdk/consts.py
CHANGED
|
@@ -47,12 +47,9 @@ if TYPE_CHECKING:
|
|
|
47
47
|
Event,
|
|
48
48
|
EventProcessor,
|
|
49
49
|
Hint,
|
|
50
|
-
MeasurementUnit,
|
|
51
50
|
ProfilerMode,
|
|
52
51
|
TracesSampler,
|
|
53
52
|
TransactionProcessor,
|
|
54
|
-
MetricTags,
|
|
55
|
-
MetricValue,
|
|
56
53
|
)
|
|
57
54
|
|
|
58
55
|
# Experiments are feature flags to enable and disable certain unstable SDK
|
|
@@ -73,11 +70,6 @@ if TYPE_CHECKING:
|
|
|
73
70
|
"transport_compression_algo": Optional[CompressionAlgo],
|
|
74
71
|
"transport_num_pools": Optional[int],
|
|
75
72
|
"transport_http2": Optional[bool],
|
|
76
|
-
"enable_metrics": Optional[bool],
|
|
77
|
-
"before_emit_metric": Optional[
|
|
78
|
-
Callable[[str, MetricValue, MeasurementUnit, MetricTags], bool]
|
|
79
|
-
],
|
|
80
|
-
"metric_code_locations": Optional[bool],
|
|
81
73
|
"enable_logs": Optional[bool],
|
|
82
74
|
},
|
|
83
75
|
total=False,
|
|
@@ -96,11 +88,6 @@ FALSE_VALUES = [
|
|
|
96
88
|
]
|
|
97
89
|
|
|
98
90
|
|
|
99
|
-
class INSTRUMENTER:
|
|
100
|
-
SENTRY = "sentry"
|
|
101
|
-
OTEL = "otel"
|
|
102
|
-
|
|
103
|
-
|
|
104
91
|
class SPANDATA:
|
|
105
92
|
"""
|
|
106
93
|
Additional information describing the type of the span.
|
|
@@ -174,7 +161,7 @@ class SPANDATA:
|
|
|
174
161
|
|
|
175
162
|
AI_TOOL_CALLS = "ai.tool_calls"
|
|
176
163
|
"""
|
|
177
|
-
For an AI model call, the function that was called.
|
|
164
|
+
For an AI model call, the function that was called.
|
|
178
165
|
"""
|
|
179
166
|
|
|
180
167
|
AI_TOOLS = "ai.tools"
|
|
@@ -491,6 +478,46 @@ class OP:
|
|
|
491
478
|
SOCKET_DNS = "socket.dns"
|
|
492
479
|
|
|
493
480
|
|
|
481
|
+
BAGGAGE_HEADER_NAME = "baggage"
|
|
482
|
+
SENTRY_TRACE_HEADER_NAME = "sentry-trace"
|
|
483
|
+
|
|
484
|
+
DEFAULT_SPAN_ORIGIN = "manual"
|
|
485
|
+
DEFAULT_SPAN_NAME = "<unlabeled span>"
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# Transaction source
|
|
489
|
+
# see https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
|
|
490
|
+
class TransactionSource(str, Enum):
|
|
491
|
+
COMPONENT = "component"
|
|
492
|
+
CUSTOM = "custom"
|
|
493
|
+
ROUTE = "route"
|
|
494
|
+
TASK = "task"
|
|
495
|
+
URL = "url"
|
|
496
|
+
VIEW = "view"
|
|
497
|
+
|
|
498
|
+
def __str__(self):
|
|
499
|
+
# type: () -> str
|
|
500
|
+
return self.value
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
# These are typically high cardinality and the server hates them
|
|
504
|
+
LOW_QUALITY_TRANSACTION_SOURCES = [
|
|
505
|
+
TransactionSource.URL,
|
|
506
|
+
]
|
|
507
|
+
|
|
508
|
+
SOURCE_FOR_STYLE = {
|
|
509
|
+
"endpoint": TransactionSource.COMPONENT,
|
|
510
|
+
"function_name": TransactionSource.COMPONENT,
|
|
511
|
+
"handler_name": TransactionSource.COMPONENT,
|
|
512
|
+
"method_and_path_pattern": TransactionSource.ROUTE,
|
|
513
|
+
"path": TransactionSource.URL,
|
|
514
|
+
"route_name": TransactionSource.COMPONENT,
|
|
515
|
+
"route_pattern": TransactionSource.ROUTE,
|
|
516
|
+
"uri_template": TransactionSource.ROUTE,
|
|
517
|
+
"url": TransactionSource.ROUTE,
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
|
|
494
521
|
# This type exists to trick mypy and PyCharm into thinking `init` and `Client`
|
|
495
522
|
# take these arguments (even though they take opaque **kwargs)
|
|
496
523
|
class ClientConstructor:
|
|
@@ -524,7 +551,6 @@ class ClientConstructor:
|
|
|
524
551
|
debug=None, # type: Optional[bool]
|
|
525
552
|
attach_stacktrace=False, # type: bool
|
|
526
553
|
ca_certs=None, # type: Optional[str]
|
|
527
|
-
propagate_traces=True, # type: bool
|
|
528
554
|
traces_sample_rate=None, # type: Optional[float]
|
|
529
555
|
traces_sampler=None, # type: Optional[TracesSampler]
|
|
530
556
|
profiles_sample_rate=None, # type: Optional[float]
|
|
@@ -538,10 +564,8 @@ class ClientConstructor:
|
|
|
538
564
|
send_client_reports=True, # type: bool
|
|
539
565
|
_experiments={}, # type: Experiments # noqa: B006
|
|
540
566
|
proxy_headers=None, # type: Optional[Dict[str, str]]
|
|
541
|
-
instrumenter=INSTRUMENTER.SENTRY, # type: Optional[str]
|
|
542
567
|
before_send_transaction=None, # type: Optional[TransactionProcessor]
|
|
543
568
|
project_root=None, # type: Optional[str]
|
|
544
|
-
enable_tracing=None, # type: Optional[bool]
|
|
545
569
|
include_local_variables=True, # type: Optional[bool]
|
|
546
570
|
include_source_context=True, # type: Optional[bool]
|
|
547
571
|
trace_propagation_targets=[ # noqa: B006
|
|
@@ -930,11 +954,6 @@ class ClientConstructor:
|
|
|
930
954
|
|
|
931
955
|
:param profile_session_sample_rate:
|
|
932
956
|
|
|
933
|
-
|
|
934
|
-
:param enable_tracing:
|
|
935
|
-
|
|
936
|
-
:param propagate_traces:
|
|
937
|
-
|
|
938
957
|
:param auto_session_tracking:
|
|
939
958
|
|
|
940
959
|
:param spotlight:
|
|
@@ -966,4 +985,4 @@ DEFAULT_OPTIONS = _get_default_options()
|
|
|
966
985
|
del _get_default_options
|
|
967
986
|
|
|
968
987
|
|
|
969
|
-
VERSION = "
|
|
988
|
+
VERSION = "3.0.0a1"
|
sentry_sdk/debug.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import logging
|
|
3
|
-
import warnings
|
|
4
3
|
|
|
5
4
|
from sentry_sdk import get_client
|
|
6
5
|
from sentry_sdk.client import _client_init_debug
|
|
@@ -30,12 +29,3 @@ def configure_logger():
|
|
|
30
29
|
logger.addHandler(_handler)
|
|
31
30
|
logger.setLevel(logging.DEBUG)
|
|
32
31
|
logger.addFilter(_DebugFilter())
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def configure_debug_hub():
|
|
36
|
-
# type: () -> None
|
|
37
|
-
warnings.warn(
|
|
38
|
-
"configure_debug_hub is deprecated. Please remove calls to it, as it is a no-op.",
|
|
39
|
-
DeprecationWarning,
|
|
40
|
-
stacklevel=2,
|
|
41
|
-
)
|
sentry_sdk/envelope.py
CHANGED
|
@@ -106,12 +106,6 @@ class Envelope:
|
|
|
106
106
|
# type: (...) -> None
|
|
107
107
|
self.add_item(Item(payload=PayloadRef(json=sessions), type="sessions"))
|
|
108
108
|
|
|
109
|
-
def add_log(
|
|
110
|
-
self, log # type: Any
|
|
111
|
-
):
|
|
112
|
-
# type: (...) -> None
|
|
113
|
-
self.add_item(Item(payload=PayloadRef(json=log), type="otel_log"))
|
|
114
|
-
|
|
115
109
|
def add_item(
|
|
116
110
|
self, item # type: Item
|
|
117
111
|
):
|
|
@@ -278,7 +272,7 @@ class Item:
|
|
|
278
272
|
return "transaction"
|
|
279
273
|
elif ty == "event":
|
|
280
274
|
return "error"
|
|
281
|
-
elif ty == "
|
|
275
|
+
elif ty == "log":
|
|
282
276
|
return "log"
|
|
283
277
|
elif ty == "client_report":
|
|
284
278
|
return "internal"
|
|
@@ -286,8 +280,6 @@ class Item:
|
|
|
286
280
|
return "profile"
|
|
287
281
|
elif ty == "profile_chunk":
|
|
288
282
|
return "profile_chunk"
|
|
289
|
-
elif ty == "statsd":
|
|
290
|
-
return "metric_bucket"
|
|
291
283
|
elif ty == "check_in":
|
|
292
284
|
return "monitor"
|
|
293
285
|
else:
|
|
@@ -347,7 +339,7 @@ class Item:
|
|
|
347
339
|
# if no length was specified we need to read up to the end of line
|
|
348
340
|
# and remove it (if it is present, i.e. not the very last char in an eof terminated envelope)
|
|
349
341
|
payload = f.readline().rstrip(b"\n")
|
|
350
|
-
if headers.get("type") in ("event", "transaction"
|
|
342
|
+
if headers.get("type") in ("event", "transaction"):
|
|
351
343
|
rv = cls(headers=headers, payload=PayloadRef(json=parse_json(payload)))
|
|
352
344
|
else:
|
|
353
345
|
rv = cls(headers=headers, payload=payload)
|
sentry_sdk/feature_flags.py
CHANGED
|
@@ -64,5 +64,9 @@ def add_feature_flag(flag, result):
|
|
|
64
64
|
Records a flag and its value to be sent on subsequent error events.
|
|
65
65
|
We recommend you do this on flag evaluations. Flags are buffered per Sentry scope.
|
|
66
66
|
"""
|
|
67
|
-
flags = sentry_sdk.
|
|
67
|
+
flags = sentry_sdk.get_isolation_scope().flags
|
|
68
68
|
flags.set(flag, result)
|
|
69
|
+
|
|
70
|
+
span = sentry_sdk.get_current_span()
|
|
71
|
+
if span:
|
|
72
|
+
span.set_flag(flag, result)
|
|
@@ -131,9 +131,11 @@ _MIN_VERSIONS = {
|
|
|
131
131
|
"celery": (4, 4, 7),
|
|
132
132
|
"chalice": (1, 16, 0),
|
|
133
133
|
"clickhouse_driver": (0, 2, 0),
|
|
134
|
-
"
|
|
134
|
+
"common": (1, 4, 0), # opentelemetry-sdk
|
|
135
|
+
"cohere": (5, 4, 0),
|
|
136
|
+
"django": (2, 0),
|
|
135
137
|
"dramatiq": (1, 9),
|
|
136
|
-
"falcon": (
|
|
138
|
+
"falcon": (3, 0),
|
|
137
139
|
"fastapi": (0, 79, 0),
|
|
138
140
|
"flask": (1, 1, 4),
|
|
139
141
|
"gql": (3, 4, 1),
|
|
@@ -156,6 +158,7 @@ _MIN_VERSIONS = {
|
|
|
156
158
|
"statsig": (0, 55, 3),
|
|
157
159
|
"strawberry": (0, 209, 5),
|
|
158
160
|
"tornado": (6, 0),
|
|
161
|
+
"trytond": (5, 0),
|
|
159
162
|
"typer": (0, 15),
|
|
160
163
|
"unleash": (6, 0, 1),
|
|
161
164
|
}
|
|
@@ -21,7 +21,7 @@ def _get_headers(asgi_scope):
|
|
|
21
21
|
Extract headers from the ASGI scope, in the format that the Sentry protocol expects.
|
|
22
22
|
"""
|
|
23
23
|
headers = {} # type: Dict[str, str]
|
|
24
|
-
for raw_key, raw_value in asgi_scope
|
|
24
|
+
for raw_key, raw_value in asgi_scope.get("headers", {}):
|
|
25
25
|
key = raw_key.decode("latin-1")
|
|
26
26
|
value = raw_value.decode("latin-1")
|
|
27
27
|
if key in headers:
|
|
@@ -32,8 +32,8 @@ def _get_headers(asgi_scope):
|
|
|
32
32
|
return headers
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def _get_url(asgi_scope, default_scheme, host):
|
|
36
|
-
# type: (Dict[str, Any], Literal["ws", "http"], Optional[Union[AnnotatedValue, str]]) -> str
|
|
35
|
+
def _get_url(asgi_scope, default_scheme=None, host=None):
|
|
36
|
+
# type: (Dict[str, Any], Optional[Literal["ws", "http"]], Optional[Union[AnnotatedValue, str]]) -> str
|
|
37
37
|
"""
|
|
38
38
|
Extract URL from the ASGI scope, without also including the querystring.
|
|
39
39
|
"""
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
from contextlib import contextmanager
|
|
2
1
|
import json
|
|
3
2
|
from copy import deepcopy
|
|
4
3
|
|
|
5
4
|
import sentry_sdk
|
|
6
5
|
from sentry_sdk.scope import should_send_default_pii
|
|
7
|
-
from sentry_sdk.utils import AnnotatedValue,
|
|
6
|
+
from sentry_sdk.utils import AnnotatedValue, SENSITIVE_DATA_SUBSTITUTE
|
|
8
7
|
|
|
9
8
|
try:
|
|
10
9
|
from django.http.request import RawPostDataException
|
|
@@ -16,12 +15,11 @@ from typing import TYPE_CHECKING
|
|
|
16
15
|
if TYPE_CHECKING:
|
|
17
16
|
from typing import Any
|
|
18
17
|
from typing import Dict
|
|
19
|
-
from typing import Iterator
|
|
20
18
|
from typing import Mapping
|
|
21
19
|
from typing import MutableMapping
|
|
22
20
|
from typing import Optional
|
|
23
21
|
from typing import Union
|
|
24
|
-
from sentry_sdk._types import Event
|
|
22
|
+
from sentry_sdk._types import Event
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
SENSITIVE_ENV_KEYS = (
|
|
@@ -52,13 +50,6 @@ DEFAULT_HTTP_METHODS_TO_CAPTURE = (
|
|
|
52
50
|
)
|
|
53
51
|
|
|
54
52
|
|
|
55
|
-
# This noop context manager can be replaced with "from contextlib import nullcontext" when we drop Python 3.6 support
|
|
56
|
-
@contextmanager
|
|
57
|
-
def nullcontext():
|
|
58
|
-
# type: () -> Iterator[None]
|
|
59
|
-
yield
|
|
60
|
-
|
|
61
|
-
|
|
62
53
|
def request_body_within_bounds(client, content_length):
|
|
63
54
|
# type: (Optional[sentry_sdk.client.BaseClient], int) -> bool
|
|
64
55
|
if client is None:
|
|
@@ -237,35 +228,15 @@ def _filter_headers(headers):
|
|
|
237
228
|
}
|
|
238
229
|
|
|
239
230
|
|
|
240
|
-
def
|
|
241
|
-
# type: (
|
|
242
|
-
|
|
243
|
-
if isinstance(target, int):
|
|
244
|
-
if code == target:
|
|
245
|
-
return True
|
|
246
|
-
continue
|
|
247
|
-
|
|
248
|
-
try:
|
|
249
|
-
if code in target:
|
|
250
|
-
return True
|
|
251
|
-
except TypeError:
|
|
252
|
-
logger.warning(
|
|
253
|
-
"failed_request_status_codes has to be a list of integers or containers"
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
return False
|
|
257
|
-
|
|
231
|
+
def _request_headers_to_span_attributes(headers):
|
|
232
|
+
# type: (dict[str, str]) -> dict[str, str]
|
|
233
|
+
attributes = {}
|
|
258
234
|
|
|
259
|
-
|
|
260
|
-
"""
|
|
261
|
-
Wrapper to make it possible to use list[HttpStatusCodeRange] as a Container[int].
|
|
262
|
-
Used for backwards compatibility with the old `failed_request_status_codes` option.
|
|
263
|
-
"""
|
|
235
|
+
headers = _filter_headers(headers)
|
|
264
236
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
237
|
+
for header, value in headers.items():
|
|
238
|
+
if isinstance(value, AnnotatedValue):
|
|
239
|
+
value = SENSITIVE_DATA_SUBSTITUTE
|
|
240
|
+
attributes[f"http.request.header.{header.lower()}"] = value
|
|
268
241
|
|
|
269
|
-
|
|
270
|
-
# type: (object) -> bool
|
|
271
|
-
return _in_http_status_code_range(item, self._code_ranges)
|
|
242
|
+
return attributes
|
|
@@ -3,8 +3,14 @@ import weakref
|
|
|
3
3
|
from functools import wraps
|
|
4
4
|
|
|
5
5
|
import sentry_sdk
|
|
6
|
-
from sentry_sdk.
|
|
7
|
-
|
|
6
|
+
from sentry_sdk.consts import (
|
|
7
|
+
OP,
|
|
8
|
+
SPANSTATUS,
|
|
9
|
+
SPANDATA,
|
|
10
|
+
BAGGAGE_HEADER_NAME,
|
|
11
|
+
SOURCE_FOR_STYLE,
|
|
12
|
+
TransactionSource,
|
|
13
|
+
)
|
|
8
14
|
from sentry_sdk.integrations import (
|
|
9
15
|
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
|
|
10
16
|
_check_minimum_version,
|
|
@@ -15,22 +21,20 @@ from sentry_sdk.integrations.logging import ignore_logger
|
|
|
15
21
|
from sentry_sdk.sessions import track_session
|
|
16
22
|
from sentry_sdk.integrations._wsgi_common import (
|
|
17
23
|
_filter_headers,
|
|
24
|
+
_request_headers_to_span_attributes,
|
|
18
25
|
request_body_within_bounds,
|
|
19
26
|
)
|
|
20
|
-
from sentry_sdk.tracing import (
|
|
21
|
-
BAGGAGE_HEADER_NAME,
|
|
22
|
-
SOURCE_FOR_STYLE,
|
|
23
|
-
TransactionSource,
|
|
24
|
-
)
|
|
25
27
|
from sentry_sdk.tracing_utils import should_propagate_trace
|
|
26
28
|
from sentry_sdk.utils import (
|
|
27
29
|
capture_internal_exceptions,
|
|
28
30
|
ensure_integration_enabled,
|
|
29
31
|
event_from_exception,
|
|
32
|
+
http_client_status_to_breadcrumb_level,
|
|
30
33
|
logger,
|
|
31
34
|
parse_url,
|
|
32
35
|
parse_version,
|
|
33
36
|
reraise,
|
|
37
|
+
set_thread_info_from_span,
|
|
34
38
|
transaction_from_function,
|
|
35
39
|
HAS_REAL_CONTEXTVARS,
|
|
36
40
|
CONTEXTVARS_ERROR_MESSAGE,
|
|
@@ -67,6 +71,13 @@ if TYPE_CHECKING:
|
|
|
67
71
|
|
|
68
72
|
TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern")
|
|
69
73
|
|
|
74
|
+
REQUEST_PROPERTY_TO_ATTRIBUTE = {
|
|
75
|
+
"query_string": "url.query",
|
|
76
|
+
"method": "http.request.method",
|
|
77
|
+
"scheme": "url.scheme",
|
|
78
|
+
"path": "url.path",
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
|
|
71
82
|
class AioHttpIntegration(Integration):
|
|
72
83
|
identifier = "aiohttp"
|
|
@@ -123,51 +134,38 @@ class AioHttpIntegration(Integration):
|
|
|
123
134
|
scope.add_event_processor(_make_request_processor(weak_request))
|
|
124
135
|
|
|
125
136
|
headers = dict(request.headers)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
try:
|
|
160
|
-
# A valid response handler will return a valid response with a status. But, if the handler
|
|
161
|
-
# returns an invalid response (e.g. None), the line below will raise an AttributeError.
|
|
162
|
-
# Even though this is likely invalid, we need to handle this case to ensure we don't break
|
|
163
|
-
# the application.
|
|
164
|
-
response_status = response.status
|
|
165
|
-
except AttributeError:
|
|
166
|
-
pass
|
|
167
|
-
else:
|
|
168
|
-
transaction.set_http_status(response_status)
|
|
169
|
-
|
|
170
|
-
return response
|
|
137
|
+
with sentry_sdk.continue_trace(headers):
|
|
138
|
+
with sentry_sdk.start_span(
|
|
139
|
+
op=OP.HTTP_SERVER,
|
|
140
|
+
# If this transaction name makes it to the UI, AIOHTTP's
|
|
141
|
+
# URL resolver did not find a route or died trying.
|
|
142
|
+
name="generic AIOHTTP request",
|
|
143
|
+
source=TransactionSource.ROUTE,
|
|
144
|
+
origin=AioHttpIntegration.origin,
|
|
145
|
+
attributes=_prepopulate_attributes(request),
|
|
146
|
+
) as span:
|
|
147
|
+
try:
|
|
148
|
+
response = await old_handle(self, request)
|
|
149
|
+
except HTTPException as e:
|
|
150
|
+
span.set_http_status(e.status_code)
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
e.status_code
|
|
154
|
+
in integration._failed_request_status_codes
|
|
155
|
+
):
|
|
156
|
+
_capture_exception()
|
|
157
|
+
|
|
158
|
+
raise
|
|
159
|
+
except (asyncio.CancelledError, ConnectionResetError):
|
|
160
|
+
span.set_status(SPANSTATUS.CANCELLED)
|
|
161
|
+
raise
|
|
162
|
+
except Exception:
|
|
163
|
+
# This will probably map to a 500 but seems like we
|
|
164
|
+
# have no way to tell. Do not set span status.
|
|
165
|
+
reraise(*_capture_exception())
|
|
166
|
+
|
|
167
|
+
span.set_http_status(response.status)
|
|
168
|
+
return response
|
|
171
169
|
|
|
172
170
|
Application._handle = sentry_app_handle
|
|
173
171
|
|
|
@@ -238,12 +236,21 @@ def create_trace_config():
|
|
|
238
236
|
name="%s %s"
|
|
239
237
|
% (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE),
|
|
240
238
|
origin=AioHttpIntegration.origin,
|
|
239
|
+
only_if_parent=True,
|
|
241
240
|
)
|
|
242
|
-
|
|
241
|
+
|
|
242
|
+
data = {
|
|
243
|
+
SPANDATA.HTTP_METHOD: method,
|
|
244
|
+
}
|
|
245
|
+
set_thread_info_from_span(data, span)
|
|
246
|
+
|
|
243
247
|
if parsed_url is not None:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
data["url"] = parsed_url.url
|
|
249
|
+
data[SPANDATA.HTTP_QUERY] = parsed_url.query
|
|
250
|
+
data[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
|
|
251
|
+
|
|
252
|
+
for key, value in data.items():
|
|
253
|
+
span.set_attribute(key, value)
|
|
247
254
|
|
|
248
255
|
client = sentry_sdk.get_client()
|
|
249
256
|
|
|
@@ -268,15 +275,28 @@ def create_trace_config():
|
|
|
268
275
|
params.headers[key] = value
|
|
269
276
|
|
|
270
277
|
trace_config_ctx.span = span
|
|
278
|
+
trace_config_ctx.span_data = data
|
|
271
279
|
|
|
272
280
|
async def on_request_end(session, trace_config_ctx, params):
|
|
273
281
|
# type: (ClientSession, SimpleNamespace, TraceRequestEndParams) -> None
|
|
274
282
|
if trace_config_ctx.span is None:
|
|
275
283
|
return
|
|
276
284
|
|
|
285
|
+
span_data = trace_config_ctx.span_data or {}
|
|
286
|
+
status_code = int(params.response.status)
|
|
287
|
+
span_data[SPANDATA.HTTP_STATUS_CODE] = status_code
|
|
288
|
+
span_data["reason"] = params.response.reason
|
|
289
|
+
|
|
290
|
+
sentry_sdk.add_breadcrumb(
|
|
291
|
+
type="http",
|
|
292
|
+
category="httplib",
|
|
293
|
+
data=span_data,
|
|
294
|
+
level=http_client_status_to_breadcrumb_level(status_code),
|
|
295
|
+
)
|
|
296
|
+
|
|
277
297
|
span = trace_config_ctx.span
|
|
278
298
|
span.set_http_status(int(params.response.status))
|
|
279
|
-
span.
|
|
299
|
+
span.set_attribute("reason", params.response.reason)
|
|
280
300
|
span.finish()
|
|
281
301
|
|
|
282
302
|
trace_config = TraceConfig()
|
|
@@ -355,3 +375,30 @@ def get_aiohttp_request_data(request):
|
|
|
355
375
|
|
|
356
376
|
# request has no body
|
|
357
377
|
return None
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _prepopulate_attributes(request):
|
|
381
|
+
# type: (Request) -> dict[str, Any]
|
|
382
|
+
"""Construct initial span attributes that can be used in traces sampler."""
|
|
383
|
+
attributes = {}
|
|
384
|
+
|
|
385
|
+
for prop, attr in REQUEST_PROPERTY_TO_ATTRIBUTE.items():
|
|
386
|
+
if getattr(request, prop, None) is not None:
|
|
387
|
+
attributes[attr] = getattr(request, prop)
|
|
388
|
+
|
|
389
|
+
if getattr(request, "host", None) is not None:
|
|
390
|
+
try:
|
|
391
|
+
host, port = request.host.split(":")
|
|
392
|
+
attributes["server.address"] = host
|
|
393
|
+
attributes["server.port"] = port
|
|
394
|
+
except ValueError:
|
|
395
|
+
attributes["server.address"] = request.host
|
|
396
|
+
|
|
397
|
+
with capture_internal_exceptions():
|
|
398
|
+
url = f"{request.scheme}://{request.host}{request.path}" # noqa: E231
|
|
399
|
+
if request.query_string:
|
|
400
|
+
attributes["url.full"] = f"{url}?{request.query_string}"
|
|
401
|
+
|
|
402
|
+
attributes.update(_request_headers_to_span_attributes(dict(request.headers)))
|
|
403
|
+
|
|
404
|
+
return attributes
|
|
@@ -121,13 +121,13 @@ def _add_ai_data_to_span(
|
|
|
121
121
|
with capture_internal_exceptions():
|
|
122
122
|
if should_send_default_pii() and integration.include_prompts:
|
|
123
123
|
complete_message = "".join(content_blocks)
|
|
124
|
-
span.
|
|
124
|
+
span.set_attribute(
|
|
125
125
|
SPANDATA.AI_RESPONSES,
|
|
126
126
|
[{"type": "text", "text": complete_message}],
|
|
127
127
|
)
|
|
128
128
|
total_tokens = input_tokens + output_tokens
|
|
129
129
|
record_token_usage(span, input_tokens, output_tokens, total_tokens)
|
|
130
|
-
span.
|
|
130
|
+
span.set_attribute(SPANDATA.AI_STREAMING, True)
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
def _sentry_patched_create_common(f, *args, **kwargs):
|
|
@@ -148,6 +148,7 @@ def _sentry_patched_create_common(f, *args, **kwargs):
|
|
|
148
148
|
op=OP.ANTHROPIC_MESSAGES_CREATE,
|
|
149
149
|
description="Anthropic messages create",
|
|
150
150
|
origin=AnthropicIntegration.origin,
|
|
151
|
+
only_if_parent=True,
|
|
151
152
|
)
|
|
152
153
|
span.__enter__()
|
|
153
154
|
|
|
@@ -158,15 +159,17 @@ def _sentry_patched_create_common(f, *args, **kwargs):
|
|
|
158
159
|
model = kwargs.get("model")
|
|
159
160
|
|
|
160
161
|
with capture_internal_exceptions():
|
|
161
|
-
span.
|
|
162
|
-
span.
|
|
162
|
+
span.set_attribute(SPANDATA.AI_MODEL_ID, model)
|
|
163
|
+
span.set_attribute(SPANDATA.AI_STREAMING, False)
|
|
163
164
|
|
|
164
165
|
if should_send_default_pii() and integration.include_prompts:
|
|
165
|
-
span.
|
|
166
|
+
span.set_attribute(SPANDATA.AI_INPUT_MESSAGES, messages)
|
|
166
167
|
|
|
167
168
|
if hasattr(result, "content"):
|
|
168
169
|
if should_send_default_pii() and integration.include_prompts:
|
|
169
|
-
span.
|
|
170
|
+
span.set_attribute(
|
|
171
|
+
SPANDATA.AI_RESPONSES, _get_responses(result.content)
|
|
172
|
+
)
|
|
170
173
|
_calculate_token_usage(result, span)
|
|
171
174
|
span.__exit__(None, None, None)
|
|
172
175
|
|
|
@@ -214,7 +217,7 @@ def _sentry_patched_create_common(f, *args, **kwargs):
|
|
|
214
217
|
result._iterator = new_iterator()
|
|
215
218
|
|
|
216
219
|
else:
|
|
217
|
-
span.
|
|
220
|
+
span.set_attribute("unknown_response", True)
|
|
218
221
|
span.__exit__(None, None, None)
|
|
219
222
|
|
|
220
223
|
return result
|