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
@@ -5,9 +5,9 @@ import sys
5
5
  from copy import deepcopy
6
6
  from datetime import datetime, timedelta, timezone
7
7
  from os import environ
8
+ from urllib.parse import urlencode
8
9
 
9
10
  import sentry_sdk
10
- from sentry_sdk.api import continue_trace
11
11
  from sentry_sdk.consts import OP
12
12
  from sentry_sdk.scope import should_send_default_pii
13
13
  from sentry_sdk.tracing import TransactionSource
@@ -21,7 +21,10 @@ from sentry_sdk.utils import (
21
21
  reraise,
22
22
  )
23
23
  from sentry_sdk.integrations import Integration
24
- from sentry_sdk.integrations._wsgi_common import _filter_headers
24
+ from sentry_sdk.integrations._wsgi_common import (
25
+ _filter_headers,
26
+ _request_headers_to_span_attributes,
27
+ )
25
28
 
26
29
  from typing import TYPE_CHECKING
27
30
 
@@ -40,6 +43,17 @@ TIMEOUT_WARNING_BUFFER = 1500 # Buffer time required to send timeout warning to
40
43
  MILLIS_TO_SECONDS = 1000.0
41
44
 
42
45
 
46
+ EVENT_TO_ATTRIBUTES = {
47
+ "httpMethod": "http.request.method",
48
+ "queryStringParameters": "url.query",
49
+ "path": "url.path",
50
+ }
51
+
52
+ CONTEXT_TO_ATTRIBUTES = {
53
+ "function_name": "faas.name",
54
+ }
55
+
56
+
43
57
  def _wrap_init_error(init_error):
44
58
  # type: (F) -> F
45
59
  @ensure_integration_enabled(AwsLambdaIntegration, init_error)
@@ -110,6 +124,9 @@ def _wrap_handler(handler):
110
124
  configured_time = aws_context.get_remaining_time_in_millis()
111
125
 
112
126
  with sentry_sdk.isolation_scope() as scope:
127
+ scope.set_transaction_name(
128
+ aws_context.function_name, source=TransactionSource.COMPONENT
129
+ )
113
130
  timeout_thread = None
114
131
  with capture_internal_exceptions():
115
132
  scope.clear_breadcrumbs()
@@ -149,34 +166,28 @@ def _wrap_handler(handler):
149
166
  if not isinstance(headers, dict):
150
167
  headers = {}
151
168
 
152
- transaction = continue_trace(
153
- headers,
154
- op=OP.FUNCTION_AWS,
155
- name=aws_context.function_name,
156
- source=TransactionSource.COMPONENT,
157
- origin=AwsLambdaIntegration.origin,
158
- )
159
- with sentry_sdk.start_transaction(
160
- transaction,
161
- custom_sampling_context={
162
- "aws_event": aws_event,
163
- "aws_context": aws_context,
164
- },
165
- ):
166
- try:
167
- return handler(aws_event, aws_context, *args, **kwargs)
168
- except Exception:
169
- exc_info = sys.exc_info()
170
- sentry_event, hint = event_from_exception(
171
- exc_info,
172
- client_options=client.options,
173
- mechanism={"type": "aws_lambda", "handled": False},
174
- )
175
- sentry_sdk.capture_event(sentry_event, hint=hint)
176
- reraise(*exc_info)
177
- finally:
178
- if timeout_thread:
179
- timeout_thread.stop()
169
+ with sentry_sdk.continue_trace(headers):
170
+ with sentry_sdk.start_span(
171
+ op=OP.FUNCTION_AWS,
172
+ name=aws_context.function_name,
173
+ source=TransactionSource.COMPONENT,
174
+ origin=AwsLambdaIntegration.origin,
175
+ attributes=_prepopulate_attributes(request_data, aws_context),
176
+ ):
177
+ try:
178
+ return handler(aws_event, aws_context, *args, **kwargs)
179
+ except Exception:
180
+ exc_info = sys.exc_info()
181
+ sentry_event, hint = event_from_exception(
182
+ exc_info,
183
+ client_options=client.options,
184
+ mechanism={"type": "aws_lambda", "handled": False},
185
+ )
186
+ sentry_sdk.capture_event(sentry_event, hint=hint)
187
+ reraise(*exc_info)
188
+ finally:
189
+ if timeout_thread:
190
+ timeout_thread.stop()
180
191
 
181
192
  return sentry_handler # type: ignore
182
193
 
@@ -219,77 +230,44 @@ class AwsLambdaIntegration(Integration):
219
230
  )
220
231
  return
221
232
 
222
- pre_37 = hasattr(lambda_bootstrap, "handle_http_request") # Python 3.6
223
-
224
- if pre_37:
225
- old_handle_event_request = lambda_bootstrap.handle_event_request
226
-
227
- def sentry_handle_event_request(request_handler, *args, **kwargs):
228
- # type: (Any, *Any, **Any) -> Any
229
- request_handler = _wrap_handler(request_handler)
230
- return old_handle_event_request(request_handler, *args, **kwargs)
233
+ lambda_bootstrap.LambdaRuntimeClient.post_init_error = _wrap_init_error(
234
+ lambda_bootstrap.LambdaRuntimeClient.post_init_error
235
+ )
231
236
 
232
- lambda_bootstrap.handle_event_request = sentry_handle_event_request
237
+ old_handle_event_request = lambda_bootstrap.handle_event_request
233
238
 
234
- old_handle_http_request = lambda_bootstrap.handle_http_request
235
-
236
- def sentry_handle_http_request(request_handler, *args, **kwargs):
237
- # type: (Any, *Any, **Any) -> Any
238
- request_handler = _wrap_handler(request_handler)
239
- return old_handle_http_request(request_handler, *args, **kwargs)
240
-
241
- lambda_bootstrap.handle_http_request = sentry_handle_http_request
239
+ def sentry_handle_event_request( # type: ignore
240
+ lambda_runtime_client, request_handler, *args, **kwargs
241
+ ):
242
+ request_handler = _wrap_handler(request_handler)
243
+ return old_handle_event_request(
244
+ lambda_runtime_client, request_handler, *args, **kwargs
245
+ )
242
246
 
243
- # Patch to_json to drain the queue. This should work even when the
244
- # SDK is initialized inside of the handler
247
+ lambda_bootstrap.handle_event_request = sentry_handle_event_request
245
248
 
246
- old_to_json = lambda_bootstrap.to_json
249
+ # Patch the runtime client to drain the queue. This should work
250
+ # even when the SDK is initialized inside of the handler
247
251
 
248
- def sentry_to_json(*args, **kwargs):
252
+ def _wrap_post_function(f):
253
+ # type: (F) -> F
254
+ def inner(*args, **kwargs):
249
255
  # type: (*Any, **Any) -> Any
250
256
  _drain_queue()
251
- return old_to_json(*args, **kwargs)
257
+ return f(*args, **kwargs)
252
258
 
253
- lambda_bootstrap.to_json = sentry_to_json
254
- else:
255
- lambda_bootstrap.LambdaRuntimeClient.post_init_error = _wrap_init_error(
256
- lambda_bootstrap.LambdaRuntimeClient.post_init_error
257
- )
259
+ return inner # type: ignore
258
260
 
259
- old_handle_event_request = lambda_bootstrap.handle_event_request
260
-
261
- def sentry_handle_event_request( # type: ignore
262
- lambda_runtime_client, request_handler, *args, **kwargs
263
- ):
264
- request_handler = _wrap_handler(request_handler)
265
- return old_handle_event_request(
266
- lambda_runtime_client, request_handler, *args, **kwargs
267
- )
268
-
269
- lambda_bootstrap.handle_event_request = sentry_handle_event_request
270
-
271
- # Patch the runtime client to drain the queue. This should work
272
- # even when the SDK is initialized inside of the handler
273
-
274
- def _wrap_post_function(f):
275
- # type: (F) -> F
276
- def inner(*args, **kwargs):
277
- # type: (*Any, **Any) -> Any
278
- _drain_queue()
279
- return f(*args, **kwargs)
280
-
281
- return inner # type: ignore
282
-
283
- lambda_bootstrap.LambdaRuntimeClient.post_invocation_result = (
284
- _wrap_post_function(
285
- lambda_bootstrap.LambdaRuntimeClient.post_invocation_result
286
- )
261
+ lambda_bootstrap.LambdaRuntimeClient.post_invocation_result = (
262
+ _wrap_post_function(
263
+ lambda_bootstrap.LambdaRuntimeClient.post_invocation_result
287
264
  )
288
- lambda_bootstrap.LambdaRuntimeClient.post_invocation_error = (
289
- _wrap_post_function(
290
- lambda_bootstrap.LambdaRuntimeClient.post_invocation_error
291
- )
265
+ )
266
+ lambda_bootstrap.LambdaRuntimeClient.post_invocation_error = (
267
+ _wrap_post_function(
268
+ lambda_bootstrap.LambdaRuntimeClient.post_invocation_error
292
269
  )
270
+ )
293
271
 
294
272
 
295
273
  def get_lambda_bootstrap():
@@ -362,7 +340,7 @@ def _make_request_event_processor(aws_event, aws_context, configured_timeout):
362
340
  request["url"] = _get_url(aws_event, aws_context)
363
341
 
364
342
  if "queryStringParameters" in aws_event:
365
- request["query_string"] = aws_event["queryStringParameters"]
343
+ request["query_string"] = urlencode(aws_event["queryStringParameters"])
366
344
 
367
345
  if "headers" in aws_event:
368
346
  request["headers"] = _filter_headers(aws_event["headers"])
@@ -402,7 +380,9 @@ def _get_url(aws_event, aws_context):
402
380
  path = aws_event.get("path", None)
403
381
 
404
382
  headers = aws_event.get("headers")
405
- if headers is None:
383
+ # Some AWS Services (ie. EventBridge) set headers as a list
384
+ # or None, so we must ensure it is a dict
385
+ if not isinstance(headers, dict):
406
386
  headers = {}
407
387
 
408
388
  host = headers.get("Host", None)
@@ -497,3 +477,40 @@ def _event_from_error_json(error_json):
497
477
  } # type: Event
498
478
 
499
479
  return event
480
+
481
+
482
+ def _prepopulate_attributes(aws_event, aws_context):
483
+ # type: (Any, Any) -> dict[str, Any]
484
+ attributes = {
485
+ "cloud.provider": "aws",
486
+ }
487
+
488
+ for prop, attr in EVENT_TO_ATTRIBUTES.items():
489
+ if aws_event.get(prop) is not None:
490
+ if prop == "queryStringParameters":
491
+ attributes[attr] = urlencode(aws_event[prop])
492
+ else:
493
+ attributes[attr] = aws_event[prop]
494
+
495
+ for prop, attr in CONTEXT_TO_ATTRIBUTES.items():
496
+ if getattr(aws_context, prop, None) is not None:
497
+ attributes[attr] = getattr(aws_context, prop)
498
+
499
+ url = _get_url(aws_event, aws_context)
500
+ if url:
501
+ if aws_event.get("queryStringParameters"):
502
+ url += f"?{urlencode(aws_event['queryStringParameters'])}"
503
+ attributes["url.full"] = url
504
+
505
+ headers = {}
506
+ if aws_event.get("headers") and isinstance(aws_event["headers"], dict):
507
+ headers = aws_event["headers"]
508
+
509
+ if headers.get("X-Forwarded-Proto"):
510
+ attributes["network.protocol.name"] = headers["X-Forwarded-Proto"]
511
+ if headers.get("Host"):
512
+ attributes["server.address"] = headers["Host"]
513
+
514
+ attributes.update(_request_headers_to_span_attributes(headers))
515
+
516
+ return attributes
@@ -3,7 +3,6 @@ from functools import partial
3
3
  import sentry_sdk
4
4
  from sentry_sdk.consts import OP, SPANDATA
5
5
  from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
6
- from sentry_sdk.tracing import Span
7
6
  from sentry_sdk.utils import (
8
7
  capture_internal_exceptions,
9
8
  ensure_integration_enabled,
@@ -19,6 +18,8 @@ if TYPE_CHECKING:
19
18
  from typing import Optional
20
19
  from typing import Type
21
20
 
21
+ from sentry_sdk.tracing import Span
22
+
22
23
  try:
23
24
  from botocore import __version__ as BOTOCORE_VERSION # type: ignore
24
25
  from botocore.client import BaseClient # type: ignore
@@ -63,17 +64,23 @@ def _sentry_request_created(service_id, request, operation_name, **kwargs):
63
64
  op=OP.HTTP_CLIENT,
64
65
  name=description,
65
66
  origin=Boto3Integration.origin,
67
+ only_if_parent=True,
66
68
  )
67
69
 
70
+ data = {
71
+ SPANDATA.HTTP_METHOD: request.method,
72
+ }
68
73
  with capture_internal_exceptions():
69
74
  parsed_url = parse_url(request.url, sanitize=False)
70
- span.set_data("aws.request.url", parsed_url.url)
71
- span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
72
- span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
75
+ data["aws.request.url"] = parsed_url.url
76
+ data[SPANDATA.HTTP_QUERY] = parsed_url.query
77
+ data[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
78
+
79
+ for key, value in data.items():
80
+ span.set_attribute(key, value)
73
81
 
74
82
  span.set_tag("aws.service_id", service_id)
75
83
  span.set_tag("aws.operation_name", operation_name)
76
- span.set_data(SPANDATA.HTTP_METHOD, request.method)
77
84
 
78
85
  # We do it in order for subsequent http calls/retries be
79
86
  # attached to this span.
@@ -82,6 +89,7 @@ def _sentry_request_created(service_id, request, operation_name, **kwargs):
82
89
  # request.context is an open-ended data-structure
83
90
  # where we can add anything useful in request life cycle.
84
91
  request.context["_sentrysdk_span"] = span
92
+ request.context["_sentrysdk_span_data"] = data
85
93
 
86
94
 
87
95
  def _sentry_after_call(context, parsed, **kwargs):
@@ -91,20 +99,28 @@ def _sentry_after_call(context, parsed, **kwargs):
91
99
  # Span could be absent if the integration is disabled.
92
100
  if span is None:
93
101
  return
94
- span.__exit__(None, None, None)
102
+
103
+ span_data = context.pop("_sentrysdk_span_data", {})
104
+
105
+ sentry_sdk.add_breadcrumb(
106
+ type="http",
107
+ category="httplib",
108
+ data=span_data,
109
+ )
95
110
 
96
111
  body = parsed.get("Body")
97
112
  if not isinstance(body, StreamingBody):
113
+ span.__exit__(None, None, None)
98
114
  return
99
115
 
100
- streaming_span = span.start_child(
116
+ streaming_span = sentry_sdk.start_span(
101
117
  op=OP.HTTP_CLIENT_STREAM,
102
- name=span.description,
118
+ name=span.name,
103
119
  origin=Boto3Integration.origin,
120
+ only_if_parent=True,
104
121
  )
105
122
 
106
123
  orig_read = body.read
107
- orig_close = body.close
108
124
 
109
125
  def sentry_streaming_body_read(*args, **kwargs):
110
126
  # type: (*Any, **Any) -> bytes
@@ -119,6 +135,8 @@ def _sentry_after_call(context, parsed, **kwargs):
119
135
 
120
136
  body.read = sentry_streaming_body_read
121
137
 
138
+ orig_close = body.close
139
+
122
140
  def sentry_streaming_body_close(*args, **kwargs):
123
141
  # type: (*Any, **Any) -> None
124
142
  streaming_span.finish()
@@ -126,6 +144,8 @@ def _sentry_after_call(context, parsed, **kwargs):
126
144
 
127
145
  body.close = sentry_streaming_body_close
128
146
 
147
+ span.__exit__(None, None, None)
148
+
129
149
 
130
150
  def _sentry_after_call_error(context, exception, **kwargs):
131
151
  # type: (Dict[str, Any], Type[BaseException], **Any) -> None
@@ -134,4 +154,13 @@ def _sentry_after_call_error(context, exception, **kwargs):
134
154
  # Span could be absent if the integration is disabled.
135
155
  if span is None:
136
156
  return
157
+
158
+ span_data = context.pop("_sentrysdk_span_data", {})
159
+
160
+ sentry_sdk.add_breadcrumb(
161
+ type="http",
162
+ category="httplib",
163
+ data=span_data,
164
+ )
165
+
137
166
  span.__exit__(type(exception), exception, None)
@@ -1,7 +1,7 @@
1
1
  import functools
2
2
 
3
3
  import sentry_sdk
4
- from sentry_sdk.tracing import SOURCE_FOR_STYLE
4
+ from sentry_sdk.consts import SOURCE_FOR_STYLE
5
5
  from sentry_sdk.utils import (
6
6
  capture_internal_exceptions,
7
7
  ensure_integration_enabled,
@@ -4,8 +4,7 @@ from functools import wraps
4
4
 
5
5
  import sentry_sdk
6
6
  from sentry_sdk import isolation_scope
7
- from sentry_sdk.api import continue_trace
8
- from sentry_sdk.consts import OP, SPANSTATUS, SPANDATA
7
+ from sentry_sdk.consts import OP, SPANSTATUS, SPANDATA, BAGGAGE_HEADER_NAME
9
8
  from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
10
9
  from sentry_sdk.integrations.celery.beat import (
11
10
  _patch_beat_apply_entry,
@@ -14,7 +13,7 @@ from sentry_sdk.integrations.celery.beat import (
14
13
  )
15
14
  from sentry_sdk.integrations.celery.utils import _now_seconds_since_epoch
16
15
  from sentry_sdk.integrations.logging import ignore_logger
17
- from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, TransactionSource
16
+ from sentry_sdk.tracing import TransactionSource
18
17
  from sentry_sdk.tracing_utils import Baggage
19
18
  from sentry_sdk.utils import (
20
19
  capture_internal_exceptions,
@@ -113,7 +112,6 @@ def _capture_exception(task, exc_info):
113
112
  return
114
113
 
115
114
  if isinstance(exc_info[1], CELERY_CONTROL_FLOW_EXCEPTIONS):
116
- # ??? Doesn't map to anything
117
115
  _set_status("aborted")
118
116
  return
119
117
 
@@ -277,6 +275,7 @@ def _wrap_task_run(f):
277
275
  op=OP.QUEUE_SUBMIT_CELERY,
278
276
  name=task_name,
279
277
  origin=CeleryIntegration.origin,
278
+ only_if_parent=True,
280
279
  )
281
280
  if not task_started_from_beat
282
281
  else NoOpMgr()
@@ -307,40 +306,29 @@ def _wrap_tracer(task, f):
307
306
  with isolation_scope() as scope:
308
307
  scope._name = "celery"
309
308
  scope.clear_breadcrumbs()
309
+ scope.set_transaction_name(task.name, source=TransactionSource.TASK)
310
310
  scope.add_event_processor(_make_event_processor(task, *args, **kwargs))
311
311
 
312
- transaction = None
313
-
314
312
  # Celery task objects are not a thing to be trusted. Even
315
313
  # something such as attribute access can fail.
316
- with capture_internal_exceptions():
317
- headers = args[3].get("headers") or {}
318
- transaction = continue_trace(
319
- headers,
314
+ headers = args[3].get("headers") or {}
315
+
316
+ with sentry_sdk.continue_trace(headers):
317
+ with sentry_sdk.start_span(
320
318
  op=OP.QUEUE_TASK_CELERY,
321
- name="unknown celery task",
319
+ name=task.name,
322
320
  source=TransactionSource.TASK,
323
321
  origin=CeleryIntegration.origin,
324
- )
325
- transaction.name = task.name
326
- transaction.set_status(SPANSTATUS.OK)
322
+ # for some reason, args[1] is a list if non-empty but a
323
+ # tuple if empty
324
+ attributes=_prepopulate_attributes(task, list(args[1]), args[2]),
325
+ ) as root_span:
326
+ return_value = f(*args, **kwargs)
327
327
 
328
- if transaction is None:
329
- return f(*args, **kwargs)
328
+ if root_span.status is None:
329
+ root_span.set_status(SPANSTATUS.OK)
330
330
 
331
- with sentry_sdk.start_transaction(
332
- transaction,
333
- custom_sampling_context={
334
- "celery_job": {
335
- "task": task.name,
336
- # for some reason, args[1] is a list if non-empty but a
337
- # tuple if empty
338
- "args": list(args[1]),
339
- "kwargs": args[2],
340
- }
341
- },
342
- ):
343
- return f(*args, **kwargs)
331
+ return return_value
344
332
 
345
333
  return _inner # type: ignore
346
334
 
@@ -355,7 +343,7 @@ def _set_messaging_destination_name(task, span):
355
343
  if delivery_info.get("exchange") == "" and routing_key is not None:
356
344
  # Empty exchange indicates the default exchange, meaning the tasks
357
345
  # are sent to the queue with the same name as the routing key.
358
- span.set_data(SPANDATA.MESSAGING_DESTINATION_NAME, routing_key)
346
+ span.set_attribute(SPANDATA.MESSAGING_DESTINATION_NAME, routing_key)
359
347
 
360
348
 
361
349
  def _wrap_task_call(task, f):
@@ -377,6 +365,7 @@ def _wrap_task_call(task, f):
377
365
  op=OP.QUEUE_PROCESS,
378
366
  name=task.name,
379
367
  origin=CeleryIntegration.origin,
368
+ only_if_parent=True,
380
369
  ) as span:
381
370
  _set_messaging_destination_name(task, span)
382
371
 
@@ -391,23 +380,26 @@ def _wrap_task_call(task, f):
391
380
  )
392
381
 
393
382
  if latency is not None:
394
- span.set_data(SPANDATA.MESSAGING_MESSAGE_RECEIVE_LATENCY, latency)
383
+ span.set_attribute(
384
+ SPANDATA.MESSAGING_MESSAGE_RECEIVE_LATENCY, latency
385
+ )
395
386
 
396
387
  with capture_internal_exceptions():
397
- span.set_data(SPANDATA.MESSAGING_MESSAGE_ID, task.request.id)
388
+ span.set_attribute(SPANDATA.MESSAGING_MESSAGE_ID, task.request.id)
398
389
 
399
390
  with capture_internal_exceptions():
400
- span.set_data(
391
+ span.set_attribute(
401
392
  SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, task.request.retries
402
393
  )
403
394
 
404
395
  with capture_internal_exceptions():
405
- span.set_data(
396
+ span.set_attribute(
406
397
  SPANDATA.MESSAGING_SYSTEM,
407
398
  task.app.connection().transport.driver_type,
408
399
  )
409
400
 
410
401
  return f(*args, **kwargs)
402
+
411
403
  except Exception:
412
404
  exc_info = sys.exc_info()
413
405
  with capture_internal_exceptions():
@@ -506,23 +498,41 @@ def _patch_producer_publish():
506
498
  op=OP.QUEUE_PUBLISH,
507
499
  name=task_name,
508
500
  origin=CeleryIntegration.origin,
501
+ only_if_parent=True,
509
502
  ) as span:
510
503
  if task_id is not None:
511
- span.set_data(SPANDATA.MESSAGING_MESSAGE_ID, task_id)
504
+ span.set_attribute(SPANDATA.MESSAGING_MESSAGE_ID, task_id)
512
505
 
513
506
  if exchange == "" and routing_key is not None:
514
507
  # Empty exchange indicates the default exchange, meaning messages are
515
508
  # routed to the queue with the same name as the routing key.
516
- span.set_data(SPANDATA.MESSAGING_DESTINATION_NAME, routing_key)
509
+ span.set_attribute(SPANDATA.MESSAGING_DESTINATION_NAME, routing_key)
517
510
 
518
511
  if retries is not None:
519
- span.set_data(SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, retries)
512
+ span.set_attribute(SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, retries)
520
513
 
521
514
  with capture_internal_exceptions():
522
- span.set_data(
515
+ span.set_attribute(
523
516
  SPANDATA.MESSAGING_SYSTEM, self.connection.transport.driver_type
524
517
  )
525
518
 
526
519
  return original_publish(self, *args, **kwargs)
527
520
 
528
521
  Producer.publish = sentry_publish
522
+
523
+
524
+ def _prepopulate_attributes(task, args, kwargs):
525
+ # type: (Any, *Any, **Any) -> dict[str, str]
526
+ attributes = {
527
+ "celery.job.task": task.name,
528
+ }
529
+
530
+ for i, arg in enumerate(args):
531
+ with capture_internal_exceptions():
532
+ attributes[f"celery.job.args.{i}"] = str(arg)
533
+
534
+ for kwarg, value in kwargs.items():
535
+ with capture_internal_exceptions():
536
+ attributes[f"celery.job.kwargs.{kwarg}"] = str(value)
537
+
538
+ return attributes