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.

Files changed (114) 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 +8 -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 +5 -1
  14. sentry_sdk/integrations/__init__.py +5 -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 +103 -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/launchdarkly.py +3 -3
  51. sentry_sdk/integrations/litestar.py +4 -2
  52. sentry_sdk/integrations/logging.py +12 -3
  53. sentry_sdk/integrations/openai.py +2 -0
  54. sentry_sdk/integrations/openfeature.py +3 -5
  55. sentry_sdk/integrations/pymongo.py +18 -25
  56. sentry_sdk/integrations/pyramid.py +1 -1
  57. sentry_sdk/integrations/quart.py +3 -3
  58. sentry_sdk/integrations/ray.py +23 -17
  59. sentry_sdk/integrations/redis/_async_common.py +30 -18
  60. sentry_sdk/integrations/redis/_sync_common.py +28 -18
  61. sentry_sdk/integrations/redis/modules/caches.py +13 -10
  62. sentry_sdk/integrations/redis/modules/queries.py +14 -11
  63. sentry_sdk/integrations/redis/rb.py +4 -4
  64. sentry_sdk/integrations/redis/redis.py +6 -6
  65. sentry_sdk/integrations/redis/redis_cluster.py +18 -16
  66. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +4 -4
  67. sentry_sdk/integrations/redis/utils.py +63 -19
  68. sentry_sdk/integrations/rq.py +68 -23
  69. sentry_sdk/integrations/rust_tracing.py +28 -43
  70. sentry_sdk/integrations/sanic.py +23 -13
  71. sentry_sdk/integrations/socket.py +9 -5
  72. sentry_sdk/integrations/sqlalchemy.py +8 -8
  73. sentry_sdk/integrations/starlette.py +11 -31
  74. sentry_sdk/integrations/starlite.py +4 -2
  75. sentry_sdk/integrations/stdlib.py +56 -9
  76. sentry_sdk/integrations/strawberry.py +40 -59
  77. sentry_sdk/integrations/threading.py +10 -26
  78. sentry_sdk/integrations/tornado.py +57 -18
  79. sentry_sdk/integrations/trytond.py +4 -1
  80. sentry_sdk/integrations/unleash.py +2 -3
  81. sentry_sdk/integrations/wsgi.py +84 -38
  82. sentry_sdk/opentelemetry/__init__.py +9 -0
  83. sentry_sdk/opentelemetry/consts.py +33 -0
  84. sentry_sdk/opentelemetry/contextvars_context.py +73 -0
  85. sentry_sdk/{integrations/opentelemetry → opentelemetry}/propagator.py +19 -28
  86. sentry_sdk/opentelemetry/sampler.py +326 -0
  87. sentry_sdk/opentelemetry/scope.py +218 -0
  88. sentry_sdk/opentelemetry/span_processor.py +329 -0
  89. sentry_sdk/opentelemetry/tracing.py +35 -0
  90. sentry_sdk/opentelemetry/utils.py +476 -0
  91. sentry_sdk/profiler/__init__.py +0 -40
  92. sentry_sdk/profiler/continuous_profiler.py +1 -30
  93. sentry_sdk/profiler/transaction_profiler.py +5 -56
  94. sentry_sdk/scope.py +107 -351
  95. sentry_sdk/sessions.py +0 -87
  96. sentry_sdk/tracing.py +418 -1134
  97. sentry_sdk/tracing_utils.py +134 -169
  98. sentry_sdk/transport.py +4 -104
  99. sentry_sdk/types.py +26 -2
  100. sentry_sdk/utils.py +169 -152
  101. {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/METADATA +3 -5
  102. sentry_sdk-3.0.0a1.dist-info/RECORD +154 -0
  103. {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/WHEEL +1 -1
  104. sentry_sdk-3.0.0a1.dist-info/entry_points.txt +2 -0
  105. sentry_sdk/hub.py +0 -739
  106. sentry_sdk/integrations/opentelemetry/__init__.py +0 -7
  107. sentry_sdk/integrations/opentelemetry/consts.py +0 -5
  108. sentry_sdk/integrations/opentelemetry/integration.py +0 -58
  109. sentry_sdk/integrations/opentelemetry/span_processor.py +0 -391
  110. sentry_sdk/metrics.py +0 -965
  111. sentry_sdk-2.26.1.dist-info/RECORD +0 -152
  112. sentry_sdk-2.26.1.dist-info/entry_points.txt +0 -2
  113. {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/licenses/LICENSE +0 -0
  114. {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/top_level.txt +0 -0
@@ -3,9 +3,9 @@ from copy import deepcopy
3
3
  from functools import wraps
4
4
 
5
5
  import sentry_sdk
6
+ from sentry_sdk.consts import SOURCE_FOR_STYLE, TransactionSource
6
7
  from sentry_sdk.integrations import DidNotEnable
7
8
  from sentry_sdk.scope import should_send_default_pii
8
- from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
9
9
  from sentry_sdk.utils import (
10
10
  transaction_from_function,
11
11
  logger,
@@ -89,8 +89,8 @@ def patch_get_request_handler():
89
89
  def _sentry_call(*args, **kwargs):
90
90
  # type: (*Any, **Any) -> Any
91
91
  current_scope = sentry_sdk.get_current_scope()
92
- if current_scope.transaction is not None:
93
- current_scope.transaction.update_active_thread()
92
+ if current_scope.root_span is not None:
93
+ current_scope.root_span.update_active_thread()
94
94
 
95
95
  sentry_scope = sentry_sdk.get_isolation_scope()
96
96
  if sentry_scope.profile is not None:
@@ -1,4 +1,5 @@
1
1
  import sentry_sdk
2
+ from sentry_sdk.consts import SOURCE_FOR_STYLE
2
3
  from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
3
4
  from sentry_sdk.integrations._wsgi_common import (
4
5
  DEFAULT_HTTP_METHODS_TO_CAPTURE,
@@ -6,7 +7,6 @@ from sentry_sdk.integrations._wsgi_common import (
6
7
  )
7
8
  from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
8
9
  from sentry_sdk.scope import should_send_default_pii
9
- from sentry_sdk.tracing import SOURCE_FOR_STYLE
10
10
  from sentry_sdk.utils import (
11
11
  capture_internal_exceptions,
12
12
  ensure_integration_enabled,
@@ -5,10 +5,12 @@ from datetime import datetime, timedelta, timezone
5
5
  from os import environ
6
6
 
7
7
  import sentry_sdk
8
- from sentry_sdk.api import continue_trace
9
8
  from sentry_sdk.consts import OP
10
9
  from sentry_sdk.integrations import Integration
11
- from sentry_sdk.integrations._wsgi_common import _filter_headers
10
+ from sentry_sdk.integrations._wsgi_common import (
11
+ _filter_headers,
12
+ _request_headers_to_span_attributes,
13
+ )
12
14
  from sentry_sdk.scope import should_send_default_pii
13
15
  from sentry_sdk.tracing import TransactionSource
14
16
  from sentry_sdk.utils import (
@@ -84,42 +86,30 @@ def _wrap_func(func):
84
86
  if hasattr(gcp_event, "headers"):
85
87
  headers = gcp_event.headers
86
88
 
87
- transaction = continue_trace(
88
- headers,
89
- op=OP.FUNCTION_GCP,
90
- name=environ.get("FUNCTION_NAME", ""),
91
- source=TransactionSource.COMPONENT,
92
- origin=GcpIntegration.origin,
93
- )
94
- sampling_context = {
95
- "gcp_env": {
96
- "function_name": environ.get("FUNCTION_NAME"),
97
- "function_entry_point": environ.get("ENTRY_POINT"),
98
- "function_identity": environ.get("FUNCTION_IDENTITY"),
99
- "function_region": environ.get("FUNCTION_REGION"),
100
- "function_project": environ.get("GCP_PROJECT"),
101
- },
102
- "gcp_event": gcp_event,
103
- }
104
- with sentry_sdk.start_transaction(
105
- transaction, custom_sampling_context=sampling_context
106
- ):
107
- try:
108
- return func(functionhandler, gcp_event, *args, **kwargs)
109
- except Exception:
110
- exc_info = sys.exc_info()
111
- sentry_event, hint = event_from_exception(
112
- exc_info,
113
- client_options=client.options,
114
- mechanism={"type": "gcp", "handled": False},
115
- )
116
- sentry_sdk.capture_event(sentry_event, hint=hint)
117
- reraise(*exc_info)
118
- finally:
119
- if timeout_thread:
120
- timeout_thread.stop()
121
- # Flush out the event queue
122
- client.flush()
89
+ with sentry_sdk.continue_trace(headers):
90
+ with sentry_sdk.start_span(
91
+ op=OP.FUNCTION_GCP,
92
+ name=environ.get("FUNCTION_NAME", ""),
93
+ source=TransactionSource.COMPONENT,
94
+ origin=GcpIntegration.origin,
95
+ attributes=_prepopulate_attributes(gcp_event),
96
+ ):
97
+ try:
98
+ return func(functionhandler, gcp_event, *args, **kwargs)
99
+ except Exception:
100
+ exc_info = sys.exc_info()
101
+ sentry_event, hint = event_from_exception(
102
+ exc_info,
103
+ client_options=client.options,
104
+ mechanism={"type": "gcp", "handled": False},
105
+ )
106
+ sentry_sdk.capture_event(sentry_event, hint=hint)
107
+ reraise(*exc_info)
108
+ finally:
109
+ if timeout_thread:
110
+ timeout_thread.stop()
111
+ # Flush out the event queue
112
+ client.flush()
123
113
 
124
114
  return sentry_func # type: ignore
125
115
 
@@ -232,3 +222,38 @@ def _get_google_cloud_logs_url(final_time):
232
222
  )
233
223
 
234
224
  return url
225
+
226
+
227
+ ENV_TO_ATTRIBUTE = {
228
+ "FUNCTION_NAME": "faas.name",
229
+ "ENTRY_POINT": "gcp.function.entry_point",
230
+ "FUNCTION_IDENTITY": "gcp.function.identity",
231
+ "FUNCTION_REGION": "faas.region",
232
+ "GCP_PROJECT": "gcp.function.project",
233
+ }
234
+
235
+ EVENT_TO_ATTRIBUTE = {
236
+ "method": "http.request.method",
237
+ "query_string": "url.query",
238
+ }
239
+
240
+
241
+ def _prepopulate_attributes(gcp_event):
242
+ # type: (Any) -> dict[str, Any]
243
+ attributes = {
244
+ "cloud.provider": "gcp",
245
+ }
246
+
247
+ for key, attr in ENV_TO_ATTRIBUTE.items():
248
+ if environ.get(key):
249
+ attributes[attr] = environ[key]
250
+
251
+ for key, attr in EVENT_TO_ATTRIBUTE.items():
252
+ if getattr(gcp_event, key, None):
253
+ attributes[attr] = getattr(gcp_event, key)
254
+
255
+ if hasattr(gcp_event, "headers"):
256
+ headers = gcp_event.headers
257
+ attributes.update(_request_headers_to_span_attributes(headers))
258
+
259
+ return attributes
@@ -135,17 +135,10 @@ def graphql_span(schema, source, kwargs):
135
135
  },
136
136
  )
137
137
 
138
- scope = sentry_sdk.get_current_scope()
139
- if scope.span:
140
- _graphql_span = scope.span.start_child(op=op, name=operation_name)
141
- else:
142
- _graphql_span = sentry_sdk.start_span(op=op, name=operation_name)
143
-
144
- _graphql_span.set_data("graphql.document", source)
145
- _graphql_span.set_data("graphql.operation.name", operation_name)
146
- _graphql_span.set_data("graphql.operation.type", operation_type)
147
-
148
- try:
138
+ with sentry_sdk.start_span(
139
+ op=op, name=operation_name, only_if_parent=True
140
+ ) as graphql_span:
141
+ graphql_span.set_attribute("graphql.document", source)
142
+ graphql_span.set_attribute("graphql.operation.name", operation_name)
143
+ graphql_span.set_attribute("graphql.operation.type", operation_type)
149
144
  yield
150
- finally:
151
- _graphql_span.finish()
@@ -44,14 +44,17 @@ class SentryUnaryUnaryClientInterceptor(ClientInterceptor, UnaryUnaryClientInter
44
44
  request: Message,
45
45
  ) -> Union[UnaryUnaryCall, Message]:
46
46
  method = client_call_details.method
47
+ if isinstance(method, bytes):
48
+ method = method.decode()
47
49
 
48
50
  with sentry_sdk.start_span(
49
51
  op=OP.GRPC_CLIENT,
50
- name="unary unary call to %s" % method.decode(),
52
+ name="unary unary call to %s" % method,
51
53
  origin=SPAN_ORIGIN,
54
+ only_if_parent=True,
52
55
  ) as span:
53
- span.set_data("type", "unary unary")
54
- span.set_data("method", method)
56
+ span.set_attribute("type", "unary unary")
57
+ span.set_attribute("method", method)
55
58
 
56
59
  client_call_details = self._update_client_call_details_metadata_from_scope(
57
60
  client_call_details
@@ -59,7 +62,7 @@ class SentryUnaryUnaryClientInterceptor(ClientInterceptor, UnaryUnaryClientInter
59
62
 
60
63
  response = await continuation(client_call_details, request)
61
64
  status_code = await response.code()
62
- span.set_data("code", status_code.name)
65
+ span.set_attribute("code", status_code.name)
63
66
 
64
67
  return response
65
68
 
@@ -74,14 +77,17 @@ class SentryUnaryStreamClientInterceptor(
74
77
  request: Message,
75
78
  ) -> Union[AsyncIterable[Any], UnaryStreamCall]:
76
79
  method = client_call_details.method
80
+ if isinstance(method, bytes):
81
+ method = method.decode()
77
82
 
78
83
  with sentry_sdk.start_span(
79
84
  op=OP.GRPC_CLIENT,
80
- name="unary stream call to %s" % method.decode(),
85
+ name="unary stream call to %s" % method,
81
86
  origin=SPAN_ORIGIN,
87
+ only_if_parent=True,
82
88
  ) as span:
83
- span.set_data("type", "unary stream")
84
- span.set_data("method", method)
89
+ span.set_attribute("type", "unary stream")
90
+ span.set_attribute("method", method)
85
91
 
86
92
  client_call_details = self._update_client_call_details_metadata_from_scope(
87
93
  client_call_details
@@ -89,6 +95,6 @@ class SentryUnaryStreamClientInterceptor(
89
95
 
90
96
  response = await continuation(client_call_details, request)
91
97
  # status_code = await response.code()
92
- # span.set_data("code", status_code)
98
+ # span.set_attribute("code", status_code)
93
99
 
94
100
  return response
@@ -2,7 +2,7 @@ import sentry_sdk
2
2
  from sentry_sdk.consts import OP
3
3
  from sentry_sdk.integrations import DidNotEnable
4
4
  from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN
5
- from sentry_sdk.tracing import Transaction, TransactionSource
5
+ from sentry_sdk.tracing import TransactionSource
6
6
  from sentry_sdk.utils import event_from_exception
7
7
 
8
8
  from typing import TYPE_CHECKING
@@ -44,26 +44,24 @@ class ServerInterceptor(grpc.aio.ServerInterceptor): # type: ignore
44
44
  return await handler(request, context)
45
45
 
46
46
  # What if the headers are empty?
47
- transaction = Transaction.continue_from_headers(
48
- dict(context.invocation_metadata()),
49
- op=OP.GRPC_SERVER,
50
- name=name,
51
- source=TransactionSource.CUSTOM,
52
- origin=SPAN_ORIGIN,
53
- )
54
-
55
- with sentry_sdk.start_transaction(transaction=transaction):
56
- try:
57
- return await handler.unary_unary(request, context)
58
- except AbortError:
59
- raise
60
- except Exception as exc:
61
- event, hint = event_from_exception(
62
- exc,
63
- mechanism={"type": "grpc", "handled": False},
64
- )
65
- sentry_sdk.capture_event(event, hint=hint)
66
- raise
47
+ with sentry_sdk.continue_trace(dict(context.invocation_metadata())):
48
+ with sentry_sdk.start_span(
49
+ op=OP.GRPC_SERVER,
50
+ name=name,
51
+ source=TransactionSource.CUSTOM,
52
+ origin=SPAN_ORIGIN,
53
+ ):
54
+ try:
55
+ return await handler.unary_unary(request, context)
56
+ except AbortError:
57
+ raise
58
+ except Exception as exc:
59
+ event, hint = event_from_exception(
60
+ exc,
61
+ mechanism={"type": "grpc", "handled": False},
62
+ )
63
+ sentry_sdk.capture_event(event, hint=hint)
64
+ raise
67
65
 
68
66
  elif not handler.request_streaming and handler.response_streaming:
69
67
  handler_factory = grpc.unary_stream_rpc_method_handler
@@ -31,16 +31,17 @@ class ClientInterceptor(
31
31
  op=OP.GRPC_CLIENT,
32
32
  name="unary unary call to %s" % method,
33
33
  origin=SPAN_ORIGIN,
34
+ only_if_parent=True,
34
35
  ) as span:
35
- span.set_data("type", "unary unary")
36
- span.set_data("method", method)
36
+ span.set_attribute("type", "unary unary")
37
+ span.set_attribute("method", method)
37
38
 
38
39
  client_call_details = self._update_client_call_details_metadata_from_scope(
39
40
  client_call_details
40
41
  )
41
42
 
42
43
  response = continuation(client_call_details, request)
43
- span.set_data("code", response.code().name)
44
+ span.set_attribute("code", response.code().name)
44
45
 
45
46
  return response
46
47
 
@@ -52,9 +53,10 @@ class ClientInterceptor(
52
53
  op=OP.GRPC_CLIENT,
53
54
  name="unary stream call to %s" % method,
54
55
  origin=SPAN_ORIGIN,
56
+ only_if_parent=True,
55
57
  ) as span:
56
- span.set_data("type", "unary stream")
57
- span.set_data("method", method)
58
+ span.set_attribute("type", "unary stream")
59
+ span.set_attribute("method", method)
58
60
 
59
61
  client_call_details = self._update_client_call_details_metadata_from_scope(
60
62
  client_call_details
@@ -64,7 +66,7 @@ class ClientInterceptor(
64
66
  client_call_details, request
65
67
  ) # type: UnaryStreamCall
66
68
  # Setting code on unary-stream leads to execution getting stuck
67
- # span.set_data("code", response.code().name)
69
+ # span.set_attribute("code", response.code().name)
68
70
 
69
71
  return response
70
72
 
@@ -2,7 +2,7 @@ import sentry_sdk
2
2
  from sentry_sdk.consts import OP
3
3
  from sentry_sdk.integrations import DidNotEnable
4
4
  from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN
5
- from sentry_sdk.tracing import Transaction, TransactionSource
5
+ from sentry_sdk.tracing import TransactionSource
6
6
 
7
7
  from typing import TYPE_CHECKING
8
8
 
@@ -38,19 +38,17 @@ class ServerInterceptor(grpc.ServerInterceptor): # type: ignore
38
38
  if name:
39
39
  metadata = dict(context.invocation_metadata())
40
40
 
41
- transaction = Transaction.continue_from_headers(
42
- metadata,
43
- op=OP.GRPC_SERVER,
44
- name=name,
45
- source=TransactionSource.CUSTOM,
46
- origin=SPAN_ORIGIN,
47
- )
48
-
49
- with sentry_sdk.start_transaction(transaction=transaction):
50
- try:
51
- return handler.unary_unary(request, context)
52
- except BaseException as e:
53
- raise e
41
+ with sentry_sdk.continue_trace(metadata):
42
+ with sentry_sdk.start_span(
43
+ op=OP.GRPC_SERVER,
44
+ name=name,
45
+ source=TransactionSource.CUSTOM,
46
+ origin=SPAN_ORIGIN,
47
+ ):
48
+ try:
49
+ return handler.unary_unary(request, context)
50
+ except BaseException as e:
51
+ raise e
54
52
  else:
55
53
  return handler.unary_unary(request, context)
56
54
 
@@ -1,14 +1,15 @@
1
1
  import sentry_sdk
2
- from sentry_sdk.consts import OP, SPANDATA
2
+ from sentry_sdk.consts import OP, SPANDATA, BAGGAGE_HEADER_NAME
3
3
  from sentry_sdk.integrations import Integration, DidNotEnable
4
- from sentry_sdk.tracing import BAGGAGE_HEADER_NAME
5
4
  from sentry_sdk.tracing_utils import Baggage, should_propagate_trace
6
5
  from sentry_sdk.utils import (
7
6
  SENSITIVE_DATA_SUBSTITUTE,
8
7
  capture_internal_exceptions,
9
8
  ensure_integration_enabled,
9
+ http_client_status_to_breadcrumb_level,
10
10
  logger,
11
11
  parse_url,
12
+ set_thread_info_from_span,
12
13
  )
13
14
 
14
15
  from typing import TYPE_CHECKING
@@ -60,12 +61,20 @@ def _install_httpx_client():
60
61
  parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
61
62
  ),
62
63
  origin=HttpxIntegration.origin,
64
+ only_if_parent=True,
63
65
  ) as span:
64
- span.set_data(SPANDATA.HTTP_METHOD, request.method)
66
+ data = {
67
+ SPANDATA.HTTP_METHOD: request.method,
68
+ }
69
+ set_thread_info_from_span(data, span)
70
+
65
71
  if parsed_url is not None:
66
- span.set_data("url", parsed_url.url)
67
- span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
68
- span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
72
+ data["url"] = parsed_url.url
73
+ data[SPANDATA.HTTP_QUERY] = parsed_url.query
74
+ data[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
75
+
76
+ for key, value in data.items():
77
+ span.set_attribute(key, value)
69
78
 
70
79
  if should_propagate_trace(sentry_sdk.get_client(), str(request.url)):
71
80
  for (
@@ -86,7 +95,17 @@ def _install_httpx_client():
86
95
  rv = real_send(self, request, **kwargs)
87
96
 
88
97
  span.set_http_status(rv.status_code)
89
- span.set_data("reason", rv.reason_phrase)
98
+ span.set_attribute("reason", rv.reason_phrase)
99
+
100
+ data[SPANDATA.HTTP_STATUS_CODE] = rv.status_code
101
+ data["reason"] = rv.reason_phrase
102
+
103
+ sentry_sdk.add_breadcrumb(
104
+ type="http",
105
+ category="httplib",
106
+ data=data,
107
+ level=http_client_status_to_breadcrumb_level(rv.status_code),
108
+ )
90
109
 
91
110
  return rv
92
111
 
@@ -114,12 +133,18 @@ def _install_httpx_async_client():
114
133
  parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
115
134
  ),
116
135
  origin=HttpxIntegration.origin,
136
+ only_if_parent=True,
117
137
  ) as span:
118
- span.set_data(SPANDATA.HTTP_METHOD, request.method)
138
+ data = {
139
+ SPANDATA.HTTP_METHOD: request.method,
140
+ }
119
141
  if parsed_url is not None:
120
- span.set_data("url", parsed_url.url)
121
- span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
122
- span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
142
+ data["url"] = parsed_url.url
143
+ data[SPANDATA.HTTP_QUERY] = parsed_url.query
144
+ data[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
145
+
146
+ for key, value in data.items():
147
+ span.set_attribute(key, value)
123
148
 
124
149
  if should_propagate_trace(sentry_sdk.get_client(), str(request.url)):
125
150
  for (
@@ -142,7 +167,17 @@ def _install_httpx_async_client():
142
167
  rv = await real_send(self, request, **kwargs)
143
168
 
144
169
  span.set_http_status(rv.status_code)
145
- span.set_data("reason", rv.reason_phrase)
170
+ span.set_attribute("reason", rv.reason_phrase)
171
+
172
+ data[SPANDATA.HTTP_STATUS_CODE] = rv.status_code
173
+ data["reason"] = rv.reason_phrase
174
+
175
+ sentry_sdk.add_breadcrumb(
176
+ type="http",
177
+ category="httplib",
178
+ data=data,
179
+ level=http_client_status_to_breadcrumb_level(rv.status_code),
180
+ )
146
181
 
147
182
  return rv
148
183
 
@@ -2,15 +2,16 @@ import sys
2
2
  from datetime import datetime
3
3
 
4
4
  import sentry_sdk
5
- from sentry_sdk.api import continue_trace, get_baggage, get_traceparent
6
- from sentry_sdk.consts import OP, SPANSTATUS
7
- from sentry_sdk.integrations import DidNotEnable, Integration
8
- from sentry_sdk.scope import should_send_default_pii
9
- from sentry_sdk.tracing import (
5
+ from sentry_sdk.api import get_baggage, get_traceparent
6
+ from sentry_sdk.consts import (
7
+ OP,
8
+ SPANSTATUS,
10
9
  BAGGAGE_HEADER_NAME,
11
10
  SENTRY_TRACE_HEADER_NAME,
12
11
  TransactionSource,
13
12
  )
13
+ from sentry_sdk.integrations import DidNotEnable, Integration
14
+ from sentry_sdk.scope import should_send_default_pii
14
15
  from sentry_sdk.utils import (
15
16
  capture_internal_exceptions,
16
17
  ensure_integration_enabled,
@@ -61,6 +62,7 @@ def patch_enqueue():
61
62
  op=OP.QUEUE_SUBMIT_HUEY,
62
63
  name=task.name,
63
64
  origin=HueyIntegration.origin,
65
+ only_if_parent=True,
64
66
  ):
65
67
  if not isinstance(task, PeriodicTask):
66
68
  # Attach trace propagation data to task kwargs. We do
@@ -109,11 +111,13 @@ def _capture_exception(exc_info):
109
111
  # type: (ExcInfo) -> None
110
112
  scope = sentry_sdk.get_current_scope()
111
113
 
112
- if exc_info[0] in HUEY_CONTROL_FLOW_EXCEPTIONS:
113
- scope.transaction.set_status(SPANSTATUS.ABORTED)
114
- return
114
+ if scope.root_span is not None:
115
+ if exc_info[0] in HUEY_CONTROL_FLOW_EXCEPTIONS:
116
+ scope.root_span.set_status(SPANSTATUS.ABORTED)
117
+ return
118
+
119
+ scope.root_span.set_status(SPANSTATUS.INTERNAL_ERROR)
115
120
 
116
- scope.transaction.set_status(SPANSTATUS.INTERNAL_ERROR)
117
121
  event, hint = event_from_exception(
118
122
  exc_info,
119
123
  client_options=sentry_sdk.get_client().options,
@@ -135,6 +139,10 @@ def _wrap_task_execute(func):
135
139
  _capture_exception(exc_info)
136
140
  reraise(*exc_info)
137
141
 
142
+ root_span = sentry_sdk.get_current_scope().root_span
143
+ if root_span is not None:
144
+ root_span.set_status(SPANSTATUS.OK)
145
+
138
146
  return result
139
147
 
140
148
  return _sentry_execute # type: ignore
@@ -153,22 +161,18 @@ def patch_execute():
153
161
  scope.clear_breadcrumbs()
154
162
  scope.add_event_processor(_make_event_processor(task))
155
163
 
156
- sentry_headers = task.kwargs.pop("sentry_headers", None)
157
-
158
- transaction = continue_trace(
159
- sentry_headers or {},
160
- name=task.name,
161
- op=OP.QUEUE_TASK_HUEY,
162
- source=TransactionSource.TASK,
163
- origin=HueyIntegration.origin,
164
- )
165
- transaction.set_status(SPANSTATUS.OK)
166
-
167
164
  if not getattr(task, "_sentry_is_patched", False):
168
165
  task.execute = _wrap_task_execute(task.execute)
169
166
  task._sentry_is_patched = True
170
167
 
171
- with sentry_sdk.start_transaction(transaction):
172
- return old_execute(self, task, timestamp)
168
+ sentry_headers = task.kwargs.pop("sentry_headers", {})
169
+ with sentry_sdk.continue_trace(sentry_headers):
170
+ with sentry_sdk.start_span(
171
+ name=task.name,
172
+ op=OP.QUEUE_TASK_HUEY,
173
+ source=TransactionSource.TASK,
174
+ origin=HueyIntegration.origin,
175
+ ):
176
+ return old_execute(self, task, timestamp)
173
177
 
174
178
  Huey._execute = _sentry_execute
@@ -77,6 +77,7 @@ def _wrap_text_generation(f):
77
77
  op=consts.OP.HUGGINGFACE_HUB_CHAT_COMPLETIONS_CREATE,
78
78
  name="Text Generation",
79
79
  origin=HuggingfaceHubIntegration.origin,
80
+ only_if_parent=True,
80
81
  )
81
82
  span.__enter__()
82
83
  try:
@@ -3,7 +3,7 @@ from functools import wraps
3
3
 
4
4
  import sentry_sdk
5
5
  from sentry_sdk.ai.monitoring import set_ai_pipeline_name, record_token_usage
6
- from sentry_sdk.consts import OP, SPANDATA
6
+ from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
7
7
  from sentry_sdk.ai.utils import set_data_normalized
8
8
  from sentry_sdk.scope import should_send_default_pii
9
9
  from sentry_sdk.tracing import Span
@@ -72,7 +72,6 @@ class LangchainIntegration(Integration):
72
72
 
73
73
 
74
74
  class WatchedSpan:
75
- span = None # type: Span
76
75
  num_completion_tokens = 0 # type: int
77
76
  num_prompt_tokens = 0 # type: int
78
77
  no_collect_tokens = False # type: bool
@@ -123,8 +122,9 @@ class SentryLangchainCallback(BaseCallbackHandler): # type: ignore[misc]
123
122
  span_data = self.span_map[run_id]
124
123
  if not span_data:
125
124
  return
126
- sentry_sdk.capture_exception(error, span_data.span.scope)
127
- span_data.span.__exit__(None, None, None)
125
+ sentry_sdk.capture_exception(error)
126
+ span_data.span.set_status(SPANSTATUS.INTERNAL_ERROR)
127
+ span_data.span.finish()
128
128
  del self.span_map[run_id]
129
129
 
130
130
  def _normalize_langchain_message(self, message):
@@ -136,21 +136,27 @@ class SentryLangchainCallback(BaseCallbackHandler): # type: ignore[misc]
136
136
  def _create_span(self, run_id, parent_id, **kwargs):
137
137
  # type: (SentryLangchainCallback, UUID, Optional[Any], Any) -> WatchedSpan
138
138
 
139
- watched_span = None # type: Optional[WatchedSpan]
140
- if parent_id:
141
- parent_span = self.span_map.get(parent_id) # type: Optional[WatchedSpan]
142
- if parent_span:
143
- watched_span = WatchedSpan(parent_span.span.start_child(**kwargs))
144
- parent_span.children.append(watched_span)
145
- if watched_span is None:
146
- watched_span = WatchedSpan(sentry_sdk.start_span(**kwargs))
139
+ parent_watched_span = self.span_map.get(parent_id) if parent_id else None
140
+ sentry_span = sentry_sdk.start_span(
141
+ parent_span=parent_watched_span.span if parent_watched_span else None,
142
+ only_if_parent=True,
143
+ **kwargs,
144
+ )
145
+ watched_span = WatchedSpan(sentry_span)
146
+ if parent_watched_span:
147
+ parent_watched_span.children.append(watched_span)
147
148
 
148
149
  if kwargs.get("op", "").startswith("ai.pipeline."):
149
150
  if kwargs.get("name"):
150
151
  set_ai_pipeline_name(kwargs.get("name"))
151
152
  watched_span.is_pipeline = True
152
153
 
153
- watched_span.span.__enter__()
154
+ # the same run_id is reused for the pipeline it seems
155
+ # so we need to end the older span to avoid orphan spans
156
+ existing_span_data = self.span_map.get(run_id)
157
+ if existing_span_data is not None:
158
+ self._exit_span(existing_span_data, run_id)
159
+
154
160
  self.span_map[run_id] = watched_span
155
161
  self.gc_span_map()
156
162
  return watched_span
@@ -161,7 +167,8 @@ class SentryLangchainCallback(BaseCallbackHandler): # type: ignore[misc]
161
167
  if span_data.is_pipeline:
162
168
  set_ai_pipeline_name(None)
163
169
 
164
- span_data.span.__exit__(None, None, None)
170
+ span_data.span.set_status(SPANSTATUS.OK)
171
+ span_data.span.finish()
165
172
  del self.span_map[run_id]
166
173
 
167
174
  def on_llm_start(
@@ -222,7 +229,7 @@ class SentryLangchainCallback(BaseCallbackHandler): # type: ignore[misc]
222
229
  if not model and "anthropic" in all_params.get("_type"):
223
230
  model = "claude-2"
224
231
  if model:
225
- span.set_data(SPANDATA.AI_MODEL_ID, model)
232
+ span.set_attribute(SPANDATA.AI_MODEL_ID, model)
226
233
  if should_send_default_pii() and self.include_prompts:
227
234
  set_data_normalized(
228
235
  span,