sentry-sdk 2.30.0__py2.py3-none-any.whl → 3.0.0a2__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 (109) hide show
  1. sentry_sdk/__init__.py +3 -8
  2. sentry_sdk/_compat.py +0 -1
  3. sentry_sdk/_init_implementation.py +6 -44
  4. sentry_sdk/_types.py +2 -64
  5. sentry_sdk/ai/monitoring.py +14 -10
  6. sentry_sdk/ai/utils.py +1 -1
  7. sentry_sdk/api.py +56 -169
  8. sentry_sdk/client.py +27 -72
  9. sentry_sdk/consts.py +60 -23
  10. sentry_sdk/debug.py +0 -10
  11. sentry_sdk/envelope.py +1 -3
  12. sentry_sdk/feature_flags.py +1 -1
  13. sentry_sdk/integrations/__init__.py +4 -2
  14. sentry_sdk/integrations/_asgi_common.py +5 -6
  15. sentry_sdk/integrations/_wsgi_common.py +11 -40
  16. sentry_sdk/integrations/aiohttp.py +104 -57
  17. sentry_sdk/integrations/anthropic.py +10 -7
  18. sentry_sdk/integrations/arq.py +24 -13
  19. sentry_sdk/integrations/asgi.py +102 -83
  20. sentry_sdk/integrations/asyncio.py +1 -0
  21. sentry_sdk/integrations/asyncpg.py +45 -30
  22. sentry_sdk/integrations/aws_lambda.py +109 -92
  23. sentry_sdk/integrations/boto3.py +38 -9
  24. sentry_sdk/integrations/bottle.py +1 -1
  25. sentry_sdk/integrations/celery/__init__.py +51 -41
  26. sentry_sdk/integrations/clickhouse_driver.py +59 -28
  27. sentry_sdk/integrations/cohere.py +2 -0
  28. sentry_sdk/integrations/django/__init__.py +25 -46
  29. sentry_sdk/integrations/django/asgi.py +6 -2
  30. sentry_sdk/integrations/django/caching.py +13 -22
  31. sentry_sdk/integrations/django/middleware.py +1 -0
  32. sentry_sdk/integrations/django/signals_handlers.py +3 -1
  33. sentry_sdk/integrations/django/templates.py +8 -12
  34. sentry_sdk/integrations/django/transactions.py +1 -6
  35. sentry_sdk/integrations/django/views.py +5 -2
  36. sentry_sdk/integrations/falcon.py +7 -25
  37. sentry_sdk/integrations/fastapi.py +3 -3
  38. sentry_sdk/integrations/flask.py +1 -1
  39. sentry_sdk/integrations/gcp.py +63 -38
  40. sentry_sdk/integrations/graphene.py +6 -13
  41. sentry_sdk/integrations/grpc/aio/client.py +14 -8
  42. sentry_sdk/integrations/grpc/aio/server.py +19 -21
  43. sentry_sdk/integrations/grpc/client.py +8 -6
  44. sentry_sdk/integrations/grpc/server.py +12 -14
  45. sentry_sdk/integrations/httpx.py +47 -12
  46. sentry_sdk/integrations/huey.py +26 -22
  47. sentry_sdk/integrations/huggingface_hub.py +1 -0
  48. sentry_sdk/integrations/langchain.py +22 -15
  49. sentry_sdk/integrations/litestar.py +4 -2
  50. sentry_sdk/integrations/logging.py +7 -2
  51. sentry_sdk/integrations/openai.py +2 -0
  52. sentry_sdk/integrations/pymongo.py +18 -25
  53. sentry_sdk/integrations/pyramid.py +1 -1
  54. sentry_sdk/integrations/quart.py +3 -3
  55. sentry_sdk/integrations/ray.py +23 -17
  56. sentry_sdk/integrations/redis/_async_common.py +29 -18
  57. sentry_sdk/integrations/redis/_sync_common.py +28 -19
  58. sentry_sdk/integrations/redis/modules/caches.py +13 -10
  59. sentry_sdk/integrations/redis/modules/queries.py +14 -11
  60. sentry_sdk/integrations/redis/rb.py +4 -4
  61. sentry_sdk/integrations/redis/redis.py +6 -6
  62. sentry_sdk/integrations/redis/redis_cluster.py +18 -18
  63. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +4 -4
  64. sentry_sdk/integrations/redis/utils.py +64 -24
  65. sentry_sdk/integrations/rq.py +68 -23
  66. sentry_sdk/integrations/rust_tracing.py +28 -43
  67. sentry_sdk/integrations/sanic.py +23 -13
  68. sentry_sdk/integrations/socket.py +9 -5
  69. sentry_sdk/integrations/sqlalchemy.py +8 -8
  70. sentry_sdk/integrations/starlette.py +11 -31
  71. sentry_sdk/integrations/starlite.py +4 -2
  72. sentry_sdk/integrations/stdlib.py +56 -9
  73. sentry_sdk/integrations/strawberry.py +40 -59
  74. sentry_sdk/integrations/threading.py +10 -26
  75. sentry_sdk/integrations/tornado.py +57 -18
  76. sentry_sdk/integrations/trytond.py +4 -1
  77. sentry_sdk/integrations/wsgi.py +84 -38
  78. sentry_sdk/opentelemetry/__init__.py +9 -0
  79. sentry_sdk/opentelemetry/consts.py +33 -0
  80. sentry_sdk/opentelemetry/contextvars_context.py +81 -0
  81. sentry_sdk/{integrations/opentelemetry → opentelemetry}/propagator.py +19 -28
  82. sentry_sdk/opentelemetry/sampler.py +326 -0
  83. sentry_sdk/opentelemetry/scope.py +218 -0
  84. sentry_sdk/opentelemetry/span_processor.py +335 -0
  85. sentry_sdk/opentelemetry/tracing.py +59 -0
  86. sentry_sdk/opentelemetry/utils.py +484 -0
  87. sentry_sdk/profiler/__init__.py +0 -40
  88. sentry_sdk/profiler/continuous_profiler.py +1 -30
  89. sentry_sdk/profiler/transaction_profiler.py +5 -56
  90. sentry_sdk/scope.py +108 -361
  91. sentry_sdk/sessions.py +0 -87
  92. sentry_sdk/tracing.py +415 -1161
  93. sentry_sdk/tracing_utils.py +130 -166
  94. sentry_sdk/transport.py +4 -104
  95. sentry_sdk/utils.py +169 -152
  96. {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/METADATA +3 -5
  97. sentry_sdk-3.0.0a2.dist-info/RECORD +154 -0
  98. sentry_sdk-3.0.0a2.dist-info/entry_points.txt +2 -0
  99. sentry_sdk/hub.py +0 -739
  100. sentry_sdk/integrations/opentelemetry/__init__.py +0 -7
  101. sentry_sdk/integrations/opentelemetry/consts.py +0 -5
  102. sentry_sdk/integrations/opentelemetry/integration.py +0 -58
  103. sentry_sdk/integrations/opentelemetry/span_processor.py +0 -391
  104. sentry_sdk/metrics.py +0 -965
  105. sentry_sdk-2.30.0.dist-info/RECORD +0 -152
  106. sentry_sdk-2.30.0.dist-info/entry_points.txt +0 -2
  107. {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/WHEEL +0 -0
  108. {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/licenses/LICENSE +0 -0
  109. {sentry_sdk-2.30.0.dist-info → sentry_sdk-3.0.0a2.dist-info}/top_level.txt +0 -0
@@ -3,14 +3,15 @@ from sentry_sdk.consts import OP
3
3
  from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
4
4
  from sentry_sdk.integrations.redis.modules.caches import (
5
5
  _compile_cache_span_properties,
6
- _set_cache_data,
6
+ _get_cache_data,
7
7
  )
8
8
  from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
9
9
  from sentry_sdk.integrations.redis.utils import (
10
- _set_client_data,
11
- _set_pipeline_data,
10
+ _create_breadcrumb,
11
+ _get_client_data,
12
+ _get_pipeline_data,
13
+ _update_span,
12
14
  )
13
- from sentry_sdk.tracing import Span
14
15
  from sentry_sdk.utils import capture_internal_exceptions
15
16
 
16
17
  from typing import TYPE_CHECKING
@@ -24,9 +25,9 @@ def patch_redis_pipeline(
24
25
  pipeline_cls,
25
26
  is_cluster,
26
27
  get_command_args_fn,
27
- set_db_data_fn,
28
+ get_db_data_fn,
28
29
  ):
29
- # type: (Any, bool, Any, Callable[[Span, Any], None]) -> None
30
+ # type: (Any, bool, Any, Callable[[Any], dict[str, Any]]) -> None
30
31
  old_execute = pipeline_cls.execute
31
32
 
32
33
  from sentry_sdk.integrations.redis import RedisIntegration
@@ -40,30 +41,31 @@ def patch_redis_pipeline(
40
41
  op=OP.DB_REDIS,
41
42
  name="redis.pipeline.execute",
42
43
  origin=SPAN_ORIGIN,
44
+ only_if_parent=True,
43
45
  ) as span:
44
46
  with capture_internal_exceptions():
45
- command_seq = None
46
47
  try:
47
48
  command_seq = self._execution_strategy.command_queue
48
49
  except AttributeError:
49
50
  command_seq = self.command_stack
50
51
 
51
- set_db_data_fn(span, self)
52
- _set_pipeline_data(
53
- span,
54
- is_cluster,
55
- get_command_args_fn,
56
- False if is_cluster else self.transaction,
57
- command_seq,
52
+ span_data = get_db_data_fn(self)
53
+ pipeline_data = _get_pipeline_data(
54
+ is_cluster=is_cluster,
55
+ get_command_args_fn=get_command_args_fn,
56
+ is_transaction=False if is_cluster else self.transaction,
57
+ command_seq=command_seq,
58
58
  )
59
+ _update_span(span, span_data, pipeline_data)
60
+ _create_breadcrumb("redis.pipeline.execute", span_data, pipeline_data)
59
61
 
60
62
  return old_execute(self, *args, **kwargs)
61
63
 
62
64
  pipeline_cls.execute = sentry_patched_execute
63
65
 
64
66
 
65
- def patch_redis_client(cls, is_cluster, set_db_data_fn):
66
- # type: (Any, bool, Callable[[Span, Any], None]) -> None
67
+ def patch_redis_client(cls, is_cluster, get_db_data_fn):
68
+ # type: (Any, bool, Callable[[Any], dict[str, Any]]) -> None
67
69
  """
68
70
  This function can be used to instrument custom redis client classes or
69
71
  subclasses.
@@ -91,6 +93,7 @@ def patch_redis_client(cls, is_cluster, set_db_data_fn):
91
93
  op=cache_properties["op"],
92
94
  name=cache_properties["description"],
93
95
  origin=SPAN_ORIGIN,
96
+ only_if_parent=True,
94
97
  )
95
98
  cache_span.__enter__()
96
99
 
@@ -100,18 +103,24 @@ def patch_redis_client(cls, is_cluster, set_db_data_fn):
100
103
  op=db_properties["op"],
101
104
  name=db_properties["description"],
102
105
  origin=SPAN_ORIGIN,
106
+ only_if_parent=True,
103
107
  )
104
108
  db_span.__enter__()
105
109
 
106
- set_db_data_fn(db_span, self)
107
- _set_client_data(db_span, is_cluster, name, *args)
110
+ db_span_data = get_db_data_fn(self)
111
+ db_client_span_data = _get_client_data(is_cluster, name, *args)
112
+ _update_span(db_span, db_span_data, db_client_span_data)
113
+ _create_breadcrumb(
114
+ db_properties["description"], db_span_data, db_client_span_data
115
+ )
108
116
 
109
117
  value = old_execute_command(self, name, *args, **kwargs)
110
118
 
111
119
  db_span.__exit__(None, None, None)
112
120
 
113
121
  if cache_span:
114
- _set_cache_data(cache_span, self, cache_properties, value)
122
+ cache_span_data = _get_cache_data(self, cache_properties, value)
123
+ _update_span(cache_span, cache_span_data)
115
124
  cache_span.__exit__(None, None, None)
116
125
 
117
126
  return value
@@ -13,7 +13,6 @@ from typing import TYPE_CHECKING
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from sentry_sdk.integrations.redis import RedisIntegration
16
- from sentry_sdk.tracing import Span
17
16
  from typing import Any, Optional
18
17
 
19
18
 
@@ -75,22 +74,24 @@ def _get_cache_span_description(redis_command, args, kwargs, integration):
75
74
  return description
76
75
 
77
76
 
78
- def _set_cache_data(span, redis_client, properties, return_value):
79
- # type: (Span, Any, dict[str, Any], Optional[Any]) -> None
77
+ def _get_cache_data(redis_client, properties, return_value):
78
+ # type: (Any, dict[str, Any], Optional[Any]) -> dict[str, Any]
79
+ data = {}
80
+
80
81
  with capture_internal_exceptions():
81
- span.set_data(SPANDATA.CACHE_KEY, properties["key"])
82
+ data[SPANDATA.CACHE_KEY] = properties["key"]
82
83
 
83
84
  if properties["redis_command"] in GET_COMMANDS:
84
85
  if return_value is not None:
85
- span.set_data(SPANDATA.CACHE_HIT, True)
86
+ data[SPANDATA.CACHE_HIT] = True
86
87
  size = (
87
88
  len(str(return_value).encode("utf-8"))
88
89
  if not isinstance(return_value, bytes)
89
90
  else len(return_value)
90
91
  )
91
- span.set_data(SPANDATA.CACHE_ITEM_SIZE, size)
92
+ data[SPANDATA.CACHE_ITEM_SIZE] = size
92
93
  else:
93
- span.set_data(SPANDATA.CACHE_HIT, False)
94
+ data[SPANDATA.CACHE_HIT] = False
94
95
 
95
96
  elif properties["redis_command"] in SET_COMMANDS:
96
97
  if properties["value"] is not None:
@@ -99,7 +100,7 @@ def _set_cache_data(span, redis_client, properties, return_value):
99
100
  if not isinstance(properties["value"], bytes)
100
101
  else len(properties["value"])
101
102
  )
102
- span.set_data(SPANDATA.CACHE_ITEM_SIZE, size)
103
+ data[SPANDATA.CACHE_ITEM_SIZE] = size
103
104
 
104
105
  try:
105
106
  connection_params = redis_client.connection_pool.connection_kwargs
@@ -114,8 +115,10 @@ def _set_cache_data(span, redis_client, properties, return_value):
114
115
 
115
116
  host = connection_params.get("host")
116
117
  if host is not None:
117
- span.set_data(SPANDATA.NETWORK_PEER_ADDRESS, host)
118
+ data[SPANDATA.NETWORK_PEER_ADDRESS] = host
118
119
 
119
120
  port = connection_params.get("port")
120
121
  if port is not None:
121
- span.set_data(SPANDATA.NETWORK_PEER_PORT, port)
122
+ data[SPANDATA.NETWORK_PEER_PORT] = port
123
+
124
+ return data
@@ -11,7 +11,6 @@ from typing import TYPE_CHECKING
11
11
  if TYPE_CHECKING:
12
12
  from redis import Redis
13
13
  from sentry_sdk.integrations.redis import RedisIntegration
14
- from sentry_sdk.tracing import Span
15
14
  from typing import Any
16
15
 
17
16
 
@@ -43,26 +42,30 @@ def _get_db_span_description(integration, command_name, args):
43
42
  return description
44
43
 
45
44
 
46
- def _set_db_data_on_span(span, connection_params):
47
- # type: (Span, dict[str, Any]) -> None
48
- span.set_data(SPANDATA.DB_SYSTEM, "redis")
45
+ def _get_connection_data(connection_params):
46
+ # type: (dict[str, Any]) -> dict[str, Any]
47
+ data = {
48
+ SPANDATA.DB_SYSTEM: "redis",
49
+ }
49
50
 
50
51
  db = connection_params.get("db")
51
52
  if db is not None:
52
- span.set_data(SPANDATA.DB_NAME, str(db))
53
+ data[SPANDATA.DB_NAME] = str(db)
53
54
 
54
55
  host = connection_params.get("host")
55
56
  if host is not None:
56
- span.set_data(SPANDATA.SERVER_ADDRESS, host)
57
+ data[SPANDATA.SERVER_ADDRESS] = host
57
58
 
58
59
  port = connection_params.get("port")
59
60
  if port is not None:
60
- span.set_data(SPANDATA.SERVER_PORT, port)
61
+ data[SPANDATA.SERVER_PORT] = port
62
+
63
+ return data
61
64
 
62
65
 
63
- def _set_db_data(span, redis_instance):
64
- # type: (Span, Redis[Any]) -> None
66
+ def _get_db_data(redis_instance):
67
+ # type: (Redis[Any]) -> dict[str, Any]
65
68
  try:
66
- _set_db_data_on_span(span, redis_instance.connection_pool.connection_kwargs)
69
+ return _get_connection_data(redis_instance.connection_pool.connection_kwargs)
67
70
  except AttributeError:
68
- pass # connections_kwargs may be missing in some cases
71
+ return {} # connections_kwargs may be missing in some cases
@@ -5,7 +5,7 @@ https://github.com/getsentry/rb
5
5
  """
6
6
 
7
7
  from sentry_sdk.integrations.redis._sync_common import patch_redis_client
8
- from sentry_sdk.integrations.redis.modules.queries import _set_db_data
8
+ from sentry_sdk.integrations.redis.modules.queries import _get_db_data
9
9
 
10
10
 
11
11
  def _patch_rb():
@@ -18,15 +18,15 @@ def _patch_rb():
18
18
  patch_redis_client(
19
19
  rb.clients.FanoutClient,
20
20
  is_cluster=False,
21
- set_db_data_fn=_set_db_data,
21
+ get_db_data_fn=_get_db_data,
22
22
  )
23
23
  patch_redis_client(
24
24
  rb.clients.MappingClient,
25
25
  is_cluster=False,
26
- set_db_data_fn=_set_db_data,
26
+ get_db_data_fn=_get_db_data,
27
27
  )
28
28
  patch_redis_client(
29
29
  rb.clients.RoutingClient,
30
30
  is_cluster=False,
31
- set_db_data_fn=_set_db_data,
31
+ get_db_data_fn=_get_db_data,
32
32
  )
@@ -8,7 +8,7 @@ from sentry_sdk.integrations.redis._sync_common import (
8
8
  patch_redis_client,
9
9
  patch_redis_pipeline,
10
10
  )
11
- from sentry_sdk.integrations.redis.modules.queries import _set_db_data
11
+ from sentry_sdk.integrations.redis.modules.queries import _get_db_data
12
12
 
13
13
  from typing import TYPE_CHECKING
14
14
 
@@ -26,13 +26,13 @@ def _patch_redis(StrictRedis, client): # noqa: N803
26
26
  patch_redis_client(
27
27
  StrictRedis,
28
28
  is_cluster=False,
29
- set_db_data_fn=_set_db_data,
29
+ get_db_data_fn=_get_db_data,
30
30
  )
31
31
  patch_redis_pipeline(
32
32
  client.Pipeline,
33
33
  is_cluster=False,
34
34
  get_command_args_fn=_get_redis_command_args,
35
- set_db_data_fn=_set_db_data,
35
+ get_db_data_fn=_get_db_data,
36
36
  )
37
37
  try:
38
38
  strict_pipeline = client.StrictPipeline
@@ -43,7 +43,7 @@ def _patch_redis(StrictRedis, client): # noqa: N803
43
43
  strict_pipeline,
44
44
  is_cluster=False,
45
45
  get_command_args_fn=_get_redis_command_args,
46
- set_db_data_fn=_set_db_data,
46
+ get_db_data_fn=_get_db_data,
47
47
  )
48
48
 
49
49
  try:
@@ -59,11 +59,11 @@ def _patch_redis(StrictRedis, client): # noqa: N803
59
59
  patch_redis_async_client(
60
60
  redis.asyncio.client.StrictRedis,
61
61
  is_cluster=False,
62
- set_db_data_fn=_set_db_data,
62
+ get_db_data_fn=_get_db_data,
63
63
  )
64
64
  patch_redis_async_pipeline(
65
65
  redis.asyncio.client.Pipeline,
66
66
  False,
67
67
  _get_redis_command_args,
68
- set_db_data_fn=_set_db_data,
68
+ get_db_data_fn=_get_db_data,
69
69
  )
@@ -9,7 +9,7 @@ from sentry_sdk.integrations.redis._sync_common import (
9
9
  patch_redis_client,
10
10
  patch_redis_pipeline,
11
11
  )
12
- from sentry_sdk.integrations.redis.modules.queries import _set_db_data_on_span
12
+ from sentry_sdk.integrations.redis.modules.queries import _get_connection_data
13
13
  from sentry_sdk.integrations.redis.utils import _parse_rediscluster_command
14
14
 
15
15
  from sentry_sdk.utils import capture_internal_exceptions
@@ -23,18 +23,19 @@ if TYPE_CHECKING:
23
23
  RedisCluster as AsyncRedisCluster,
24
24
  ClusterPipeline as AsyncClusterPipeline,
25
25
  )
26
- from sentry_sdk.tracing import Span
27
26
 
28
27
 
29
- def _set_async_cluster_db_data(span, async_redis_cluster_instance):
30
- # type: (Span, AsyncRedisCluster[Any]) -> None
28
+ def _get_async_cluster_db_data(async_redis_cluster_instance):
29
+ # type: (AsyncRedisCluster[Any]) -> dict[str, Any]
31
30
  default_node = async_redis_cluster_instance.get_default_node()
32
31
  if default_node is not None and default_node.connection_kwargs is not None:
33
- _set_db_data_on_span(span, default_node.connection_kwargs)
32
+ return _get_connection_data(default_node.connection_kwargs)
33
+ else:
34
+ return {}
34
35
 
35
36
 
36
- def _set_async_cluster_pipeline_db_data(span, async_redis_cluster_pipeline_instance):
37
- # type: (Span, AsyncClusterPipeline[Any]) -> None
37
+ def _get_async_cluster_pipeline_db_data(async_redis_cluster_pipeline_instance):
38
+ # type: (AsyncClusterPipeline[Any]) -> dict[str, Any]
38
39
  with capture_internal_exceptions():
39
40
  client = getattr(async_redis_cluster_pipeline_instance, "cluster_client", None)
40
41
  if client is None:
@@ -46,14 +47,11 @@ def _set_async_cluster_pipeline_db_data(span, async_redis_cluster_pipeline_insta
46
47
  async_redis_cluster_pipeline_instance._client # type: ignore[attr-defined]
47
48
  )
48
49
 
49
- _set_async_cluster_db_data(
50
- span,
51
- client,
52
- )
50
+ return _get_async_cluster_db_data(client)
53
51
 
54
52
 
55
- def _set_cluster_db_data(span, redis_cluster_instance):
56
- # type: (Span, RedisCluster[Any]) -> None
53
+ def _get_cluster_db_data(redis_cluster_instance):
54
+ # type: (RedisCluster[Any]) -> dict[str, Any]
57
55
  default_node = redis_cluster_instance.get_default_node()
58
56
 
59
57
  if default_node is not None:
@@ -61,7 +59,9 @@ def _set_cluster_db_data(span, redis_cluster_instance):
61
59
  "host": default_node.host,
62
60
  "port": default_node.port,
63
61
  }
64
- _set_db_data_on_span(span, connection_params)
62
+ return _get_connection_data(connection_params)
63
+ else:
64
+ return {}
65
65
 
66
66
 
67
67
  def _patch_redis_cluster():
@@ -75,13 +75,13 @@ def _patch_redis_cluster():
75
75
  patch_redis_client(
76
76
  RedisCluster,
77
77
  is_cluster=True,
78
- set_db_data_fn=_set_cluster_db_data,
78
+ get_db_data_fn=_get_cluster_db_data,
79
79
  )
80
80
  patch_redis_pipeline(
81
81
  cluster.ClusterPipeline,
82
82
  is_cluster=True,
83
83
  get_command_args_fn=_parse_rediscluster_command,
84
- set_db_data_fn=_set_cluster_db_data,
84
+ get_db_data_fn=_get_cluster_db_data,
85
85
  )
86
86
 
87
87
  try:
@@ -97,11 +97,11 @@ def _patch_redis_cluster():
97
97
  patch_redis_async_client(
98
98
  async_cluster.RedisCluster,
99
99
  is_cluster=True,
100
- set_db_data_fn=_set_async_cluster_db_data,
100
+ get_db_data_fn=_get_async_cluster_db_data,
101
101
  )
102
102
  patch_redis_async_pipeline(
103
103
  async_cluster.ClusterPipeline,
104
104
  is_cluster=True,
105
105
  get_command_args_fn=_parse_rediscluster_command,
106
- set_db_data_fn=_set_async_cluster_pipeline_db_data,
106
+ get_db_data_fn=_get_async_cluster_pipeline_db_data,
107
107
  )
@@ -9,7 +9,7 @@ from sentry_sdk.integrations.redis._sync_common import (
9
9
  patch_redis_client,
10
10
  patch_redis_pipeline,
11
11
  )
12
- from sentry_sdk.integrations.redis.modules.queries import _set_db_data
12
+ from sentry_sdk.integrations.redis.modules.queries import _get_db_data
13
13
  from sentry_sdk.integrations.redis.utils import _parse_rediscluster_command
14
14
 
15
15
 
@@ -23,7 +23,7 @@ def _patch_rediscluster():
23
23
  patch_redis_client(
24
24
  rediscluster.RedisCluster,
25
25
  is_cluster=True,
26
- set_db_data_fn=_set_db_data,
26
+ get_db_data_fn=_get_db_data,
27
27
  )
28
28
 
29
29
  # up to v1.3.6, __version__ attribute is a tuple
@@ -37,7 +37,7 @@ def _patch_rediscluster():
37
37
  patch_redis_client(
38
38
  rediscluster.StrictRedisCluster,
39
39
  is_cluster=True,
40
- set_db_data_fn=_set_db_data,
40
+ get_db_data_fn=_get_db_data,
41
41
  )
42
42
  else:
43
43
  pipeline_cls = rediscluster.pipeline.ClusterPipeline
@@ -46,5 +46,5 @@ def _patch_rediscluster():
46
46
  pipeline_cls,
47
47
  is_cluster=True,
48
48
  get_command_args_fn=_parse_rediscluster_command,
49
- set_db_data_fn=_set_db_data,
49
+ get_db_data_fn=_get_db_data,
50
50
  )
@@ -1,3 +1,4 @@
1
+ import sentry_sdk
1
2
  from sentry_sdk.consts import SPANDATA
2
3
  from sentry_sdk.integrations.redis.consts import (
3
4
  _COMMANDS_INCLUDING_SENSITIVE_DATA,
@@ -16,6 +17,47 @@ if TYPE_CHECKING:
16
17
  from sentry_sdk.tracing import Span
17
18
 
18
19
 
20
+ TAG_KEYS = [
21
+ "redis.command",
22
+ "redis.is_cluster",
23
+ "redis.key",
24
+ "redis.transaction",
25
+ SPANDATA.DB_OPERATION,
26
+ ]
27
+
28
+
29
+ def _update_span(span, *data_bags):
30
+ # type: (Span, *dict[str, Any]) -> None
31
+ """
32
+ Set tags and data on the given span to data from the given data bags.
33
+ """
34
+ for data in data_bags:
35
+ for key, value in data.items():
36
+ if key in TAG_KEYS:
37
+ span.set_tag(key, value)
38
+ else:
39
+ span.set_attribute(key, value)
40
+
41
+
42
+ def _create_breadcrumb(message, *data_bags):
43
+ # type: (str, *dict[str, Any]) -> None
44
+ """
45
+ Create a breadcrumb containing the tags data from the given data bags.
46
+ """
47
+ data = {}
48
+ for data in data_bags:
49
+ for key, value in data.items():
50
+ if key in TAG_KEYS:
51
+ data[key] = value
52
+
53
+ sentry_sdk.add_breadcrumb(
54
+ message=message,
55
+ type="redis",
56
+ category="redis",
57
+ data=data,
58
+ )
59
+
60
+
19
61
  def _get_safe_command(name, args):
20
62
  # type: (str, Sequence[Any]) -> str
21
63
  command_parts = [name]
@@ -105,44 +147,42 @@ def _parse_rediscluster_command(command):
105
147
  return command.args
106
148
 
107
149
 
108
- def _set_pipeline_data(
109
- span,
110
- is_cluster,
111
- get_command_args_fn,
112
- is_transaction,
113
- commands_seq,
114
- ):
115
- # type: (Span, bool, Any, bool, Sequence[Any]) -> None
116
- span.set_tag("redis.is_cluster", is_cluster)
117
- span.set_tag("redis.transaction", is_transaction)
150
+ def _get_pipeline_data(is_cluster, get_command_args_fn, is_transaction, command_seq):
151
+ # type: (bool, Any, bool, Sequence[Any]) -> dict[str, Any]
152
+ data = {
153
+ "redis.is_cluster": is_cluster,
154
+ "redis.transaction": is_transaction,
155
+ } # type: dict[str, Any]
118
156
 
119
157
  commands = []
120
- for i, arg in enumerate(commands_seq):
158
+ for i, arg in enumerate(command_seq):
121
159
  if i >= _MAX_NUM_COMMANDS:
122
160
  break
123
161
 
124
162
  command = get_command_args_fn(arg)
125
163
  commands.append(_get_safe_command(command[0], command[1:]))
126
164
 
127
- span.set_data(
128
- "redis.commands",
129
- {
130
- "count": len(commands_seq),
131
- "first_ten": commands,
132
- },
133
- )
165
+ data["redis.commands.count"] = len(command_seq)
166
+ data["redis.commands.first_ten"] = commands
167
+
168
+ return data
134
169
 
135
170
 
136
- def _set_client_data(span, is_cluster, name, *args):
137
- # type: (Span, bool, str, *Any) -> None
138
- span.set_tag("redis.is_cluster", is_cluster)
171
+ def _get_client_data(is_cluster, name, *args):
172
+ # type: (bool, str, *Any) -> dict[str, Any]
173
+ data = {
174
+ "redis.is_cluster": is_cluster,
175
+ } # type: dict[str, Any]
176
+
139
177
  if name:
140
- span.set_tag("redis.command", name)
141
- span.set_tag(SPANDATA.DB_OPERATION, name)
178
+ data["redis.command"] = name
179
+ data[SPANDATA.DB_OPERATION] = name
142
180
 
143
181
  if name and args:
144
182
  name_low = name.lower()
145
183
  if (name_low in _SINGLE_KEY_COMMANDS) or (
146
184
  name_low in _MULTI_KEY_COMMANDS and len(args) == 1
147
185
  ):
148
- span.set_tag("redis.key", args[0])
186
+ data["redis.key"] = args[0]
187
+
188
+ return data
@@ -2,7 +2,6 @@ import weakref
2
2
 
3
3
  import sentry_sdk
4
4
  from sentry_sdk.consts import OP
5
- from sentry_sdk.api import continue_trace
6
5
  from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
7
6
  from sentry_sdk.integrations.logging import ignore_logger
8
7
  from sentry_sdk.tracing import TransactionSource
@@ -33,6 +32,17 @@ if TYPE_CHECKING:
33
32
 
34
33
  from rq.job import Job
35
34
 
35
+ DEFAULT_TRANSACTION_NAME = "unknown RQ task"
36
+
37
+
38
+ JOB_PROPERTY_TO_ATTRIBUTE = {
39
+ "id": "messaging.message.id",
40
+ }
41
+
42
+ QUEUE_PROPERTY_TO_ATTRIBUTE = {
43
+ "name": "messaging.destination.name",
44
+ }
45
+
36
46
 
37
47
  class RqIntegration(Integration):
38
48
  identifier = "rq"
@@ -47,28 +57,31 @@ class RqIntegration(Integration):
47
57
  old_perform_job = Worker.perform_job
48
58
 
49
59
  @ensure_integration_enabled(RqIntegration, old_perform_job)
50
- def sentry_patched_perform_job(self, job, *args, **kwargs):
51
- # type: (Any, Job, *Queue, **Any) -> bool
60
+ def sentry_patched_perform_job(self, job, queue, *args, **kwargs):
61
+ # type: (Any, Job, Queue, *Any, **Any) -> bool
52
62
  with sentry_sdk.new_scope() as scope:
53
- scope.clear_breadcrumbs()
54
- scope.add_event_processor(_make_event_processor(weakref.ref(job)))
63
+ try:
64
+ transaction_name = job.func_name or DEFAULT_TRANSACTION_NAME
65
+ except AttributeError:
66
+ transaction_name = DEFAULT_TRANSACTION_NAME
55
67
 
56
- transaction = continue_trace(
57
- job.meta.get("_sentry_trace_headers") or {},
58
- op=OP.QUEUE_TASK_RQ,
59
- name="unknown RQ task",
60
- source=TransactionSource.TASK,
61
- origin=RqIntegration.origin,
68
+ scope.set_transaction_name(
69
+ transaction_name, source=TransactionSource.TASK
62
70
  )
71
+ scope.clear_breadcrumbs()
72
+ scope.add_event_processor(_make_event_processor(weakref.ref(job)))
63
73
 
64
- with capture_internal_exceptions():
65
- transaction.name = job.func_name
66
-
67
- with sentry_sdk.start_transaction(
68
- transaction,
69
- custom_sampling_context={"rq_job": job},
74
+ with sentry_sdk.continue_trace(
75
+ job.meta.get("_sentry_trace_headers") or {}
70
76
  ):
71
- rv = old_perform_job(self, job, *args, **kwargs)
77
+ with sentry_sdk.start_span(
78
+ op=OP.QUEUE_TASK_RQ,
79
+ name=transaction_name,
80
+ source=TransactionSource.TASK,
81
+ origin=RqIntegration.origin,
82
+ attributes=_prepopulate_attributes(job, queue),
83
+ ):
84
+ rv = old_perform_job(self, job, queue, *args, **kwargs)
72
85
 
73
86
  if self.is_horse:
74
87
  # We're inside of a forked process and RQ is
@@ -102,11 +115,9 @@ class RqIntegration(Integration):
102
115
  @ensure_integration_enabled(RqIntegration, old_enqueue_job)
103
116
  def sentry_patched_enqueue_job(self, job, **kwargs):
104
117
  # type: (Queue, Any, **Any) -> Any
105
- scope = sentry_sdk.get_current_scope()
106
- if scope.span is not None:
107
- job.meta["_sentry_trace_headers"] = dict(
108
- scope.iter_trace_propagation_headers()
109
- )
118
+ job.meta["_sentry_trace_headers"] = dict(
119
+ sentry_sdk.get_current_scope().iter_trace_propagation_headers()
120
+ )
110
121
 
111
122
  return old_enqueue_job(self, job, **kwargs)
112
123
 
@@ -159,3 +170,37 @@ def _capture_exception(exc_info, **kwargs):
159
170
  )
160
171
 
161
172
  sentry_sdk.capture_event(event, hint=hint)
173
+
174
+
175
+ def _prepopulate_attributes(job, queue):
176
+ # type: (Job, Queue) -> dict[str, Any]
177
+ attributes = {
178
+ "messaging.system": "rq",
179
+ "rq.job.id": job.id,
180
+ }
181
+
182
+ for prop, attr in JOB_PROPERTY_TO_ATTRIBUTE.items():
183
+ if getattr(job, prop, None) is not None:
184
+ attributes[attr] = getattr(job, prop)
185
+
186
+ for prop, attr in QUEUE_PROPERTY_TO_ATTRIBUTE.items():
187
+ if getattr(queue, prop, None) is not None:
188
+ attributes[attr] = getattr(queue, prop)
189
+
190
+ if getattr(job, "args", None):
191
+ for i, arg in enumerate(job.args):
192
+ with capture_internal_exceptions():
193
+ attributes[f"rq.job.args.{i}"] = str(arg)
194
+
195
+ if getattr(job, "kwargs", None):
196
+ for kwarg, value in job.kwargs.items():
197
+ with capture_internal_exceptions():
198
+ attributes[f"rq.job.kwargs.{kwarg}"] = str(value)
199
+
200
+ func = job.func
201
+ if callable(func):
202
+ func = func.__name__
203
+
204
+ attributes["rq.job.func"] = str(func)
205
+
206
+ return attributes