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.

Files changed (110) 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 +2 -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 +2 -2
  14. sentry_sdk/integrations/__init__.py +4 -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 +102 -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/litestar.py +4 -2
  51. sentry_sdk/integrations/logging.py +12 -3
  52. sentry_sdk/integrations/openai.py +2 -0
  53. sentry_sdk/integrations/pymongo.py +18 -25
  54. sentry_sdk/integrations/pyramid.py +1 -1
  55. sentry_sdk/integrations/quart.py +3 -3
  56. sentry_sdk/integrations/ray.py +23 -17
  57. sentry_sdk/integrations/redis/_async_common.py +30 -18
  58. sentry_sdk/integrations/redis/_sync_common.py +28 -18
  59. sentry_sdk/integrations/redis/modules/caches.py +13 -10
  60. sentry_sdk/integrations/redis/modules/queries.py +14 -11
  61. sentry_sdk/integrations/redis/rb.py +4 -4
  62. sentry_sdk/integrations/redis/redis.py +6 -6
  63. sentry_sdk/integrations/redis/redis_cluster.py +18 -16
  64. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +4 -4
  65. sentry_sdk/integrations/redis/utils.py +63 -19
  66. sentry_sdk/integrations/rq.py +68 -23
  67. sentry_sdk/integrations/rust_tracing.py +28 -43
  68. sentry_sdk/integrations/sanic.py +23 -13
  69. sentry_sdk/integrations/socket.py +9 -5
  70. sentry_sdk/integrations/sqlalchemy.py +8 -8
  71. sentry_sdk/integrations/starlette.py +11 -31
  72. sentry_sdk/integrations/starlite.py +4 -2
  73. sentry_sdk/integrations/stdlib.py +56 -9
  74. sentry_sdk/integrations/strawberry.py +40 -59
  75. sentry_sdk/integrations/threading.py +10 -26
  76. sentry_sdk/integrations/tornado.py +57 -18
  77. sentry_sdk/integrations/trytond.py +4 -1
  78. sentry_sdk/integrations/wsgi.py +84 -38
  79. sentry_sdk/opentelemetry/__init__.py +9 -0
  80. sentry_sdk/opentelemetry/consts.py +33 -0
  81. sentry_sdk/opentelemetry/contextvars_context.py +73 -0
  82. sentry_sdk/{integrations/opentelemetry → opentelemetry}/propagator.py +19 -28
  83. sentry_sdk/opentelemetry/sampler.py +326 -0
  84. sentry_sdk/opentelemetry/scope.py +218 -0
  85. sentry_sdk/opentelemetry/span_processor.py +329 -0
  86. sentry_sdk/opentelemetry/tracing.py +35 -0
  87. sentry_sdk/opentelemetry/utils.py +476 -0
  88. sentry_sdk/profiler/__init__.py +0 -40
  89. sentry_sdk/profiler/continuous_profiler.py +1 -30
  90. sentry_sdk/profiler/transaction_profiler.py +5 -56
  91. sentry_sdk/scope.py +107 -351
  92. sentry_sdk/sessions.py +0 -87
  93. sentry_sdk/tracing.py +418 -1144
  94. sentry_sdk/tracing_utils.py +126 -164
  95. sentry_sdk/transport.py +4 -104
  96. sentry_sdk/utils.py +169 -152
  97. {sentry_sdk-2.27.0.dist-info → sentry_sdk-3.0.0a1.dist-info}/METADATA +3 -5
  98. sentry_sdk-3.0.0a1.dist-info/RECORD +154 -0
  99. {sentry_sdk-2.27.0.dist-info → sentry_sdk-3.0.0a1.dist-info}/WHEEL +1 -1
  100. sentry_sdk-3.0.0a1.dist-info/entry_points.txt +2 -0
  101. sentry_sdk/hub.py +0 -739
  102. sentry_sdk/integrations/opentelemetry/__init__.py +0 -7
  103. sentry_sdk/integrations/opentelemetry/consts.py +0 -5
  104. sentry_sdk/integrations/opentelemetry/integration.py +0 -58
  105. sentry_sdk/integrations/opentelemetry/span_processor.py +0 -391
  106. sentry_sdk/metrics.py +0 -965
  107. sentry_sdk-2.27.0.dist-info/RECORD +0 -152
  108. sentry_sdk-2.27.0.dist-info/entry_points.txt +0 -2
  109. {sentry_sdk-2.27.0.dist-info → sentry_sdk-3.0.0a1.dist-info}/licenses/LICENSE +0 -0
  110. {sentry_sdk-2.27.0.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. This is deprecated for OpenAI, and replaced by tool_calls
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 = "2.27.0"
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 == "otel_log":
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", "metric_buckets"):
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)
@@ -64,9 +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.get_current_scope().flags
67
+ flags = sentry_sdk.get_isolation_scope().flags
68
68
  flags.set(flag, result)
69
69
 
70
70
  span = sentry_sdk.get_current_span()
71
71
  if span:
72
- span.set_flag(f"flag.evaluation.{flag}", result)
72
+ span.set_flag(flag, result)
@@ -131,10 +131,11 @@ _MIN_VERSIONS = {
131
131
  "celery": (4, 4, 7),
132
132
  "chalice": (1, 16, 0),
133
133
  "clickhouse_driver": (0, 2, 0),
134
+ "common": (1, 4, 0), # opentelemetry-sdk
134
135
  "cohere": (5, 4, 0),
135
- "django": (1, 8),
136
+ "django": (2, 0),
136
137
  "dramatiq": (1, 9),
137
- "falcon": (1, 4),
138
+ "falcon": (3, 0),
138
139
  "fastapi": (0, 79, 0),
139
140
  "flask": (1, 1, 4),
140
141
  "gql": (3, 4, 1),
@@ -157,6 +158,7 @@ _MIN_VERSIONS = {
157
158
  "statsig": (0, 55, 3),
158
159
  "strawberry": (0, 209, 5),
159
160
  "tornado": (6, 0),
161
+ "trytond": (5, 0),
160
162
  "typer": (0, 15),
161
163
  "unleash": (6, 0, 1),
162
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["headers"]:
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, logger
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, HttpStatusCodeRange
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 _in_http_status_code_range(code, code_ranges):
241
- # type: (object, list[HttpStatusCodeRange]) -> bool
242
- for target in code_ranges:
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
- class HttpCodeRangeContainer:
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
- def __init__(self, code_ranges):
266
- # type: (list[HttpStatusCodeRange]) -> None
267
- self._code_ranges = code_ranges
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
- def __contains__(self, item):
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.api import continue_trace
7
- from sentry_sdk.consts import OP, SPANSTATUS, SPANDATA
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
- transaction = continue_trace(
127
- headers,
128
- op=OP.HTTP_SERVER,
129
- # If this transaction name makes it to the UI, AIOHTTP's
130
- # URL resolver did not find a route or died trying.
131
- name="generic AIOHTTP request",
132
- source=TransactionSource.ROUTE,
133
- origin=AioHttpIntegration.origin,
134
- )
135
- with sentry_sdk.start_transaction(
136
- transaction,
137
- custom_sampling_context={"aiohttp_request": request},
138
- ):
139
- try:
140
- response = await old_handle(self, request)
141
- except HTTPException as e:
142
- transaction.set_http_status(e.status_code)
143
-
144
- if (
145
- e.status_code
146
- in integration._failed_request_status_codes
147
- ):
148
- _capture_exception()
149
-
150
- raise
151
- except (asyncio.CancelledError, ConnectionResetError):
152
- transaction.set_status(SPANSTATUS.CANCELLED)
153
- raise
154
- except Exception:
155
- # This will probably map to a 500 but seems like we
156
- # have no way to tell. Do not set span status.
157
- reraise(*_capture_exception())
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
- span.set_data(SPANDATA.HTTP_METHOD, method)
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
- span.set_data("url", parsed_url.url)
245
- span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
246
- span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
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.set_data("reason", params.response.reason)
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.set_data(
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.set_data(SPANDATA.AI_STREAMING, True)
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.set_data(SPANDATA.AI_MODEL_ID, model)
162
- span.set_data(SPANDATA.AI_STREAMING, False)
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.set_data(SPANDATA.AI_INPUT_MESSAGES, messages)
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.set_data(SPANDATA.AI_RESPONSES, _get_responses(result.content))
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.set_data("unknown_response", True)
220
+ span.set_attribute("unknown_response", True)
218
221
  span.__exit__(None, None, None)
219
222
 
220
223
  return result