sentry-sdk 0.18.0__py2.py3-none-any.whl → 2.46.0__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.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -6
  2. sentry_sdk/_compat.py +64 -56
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +81 -19
  8. sentry_sdk/_types.py +311 -11
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +409 -67
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +849 -103
  16. sentry_sdk/consts.py +1389 -34
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +12 -15
  22. sentry_sdk/envelope.py +112 -61
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +442 -386
  25. sentry_sdk/integrations/__init__.py +228 -58
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +131 -40
  28. sentry_sdk/integrations/aiohttp.py +221 -72
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +4 -6
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +237 -135
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +13 -18
  37. sentry_sdk/integrations/aws_lambda.py +233 -80
  38. sentry_sdk/integrations/beam.py +27 -35
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +91 -69
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +35 -28
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +32 -8
  49. sentry_sdk/integrations/django/__init__.py +343 -89
  50. sentry_sdk/integrations/django/asgi.py +201 -22
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +80 -32
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +69 -2
  55. sentry_sdk/integrations/django/transactions.py +39 -14
  56. sentry_sdk/integrations/django/views.py +69 -16
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +19 -13
  59. sentry_sdk/integrations/executing.py +5 -6
  60. sentry_sdk/integrations/falcon.py +128 -65
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +114 -75
  63. sentry_sdk/integrations/gcp.py +67 -36
  64. sentry_sdk/integrations/gnu_backtrace.py +14 -22
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +261 -85
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +6 -33
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +20 -11
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +71 -60
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +62 -52
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +248 -114
  145. sentry_sdk/integrations/serverless.py +13 -22
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/spark_driver.py +115 -62
  148. sentry_sdk/integrations/spark/spark_worker.py +42 -50
  149. sentry_sdk/integrations/sqlalchemy.py +82 -37
  150. sentry_sdk/integrations/starlette.py +737 -0
  151. sentry_sdk/integrations/starlite.py +292 -0
  152. sentry_sdk/integrations/statsig.py +37 -0
  153. sentry_sdk/integrations/stdlib.py +100 -58
  154. sentry_sdk/integrations/strawberry.py +394 -0
  155. sentry_sdk/integrations/sys_exit.py +70 -0
  156. sentry_sdk/integrations/threading.py +142 -38
  157. sentry_sdk/integrations/tornado.py +68 -53
  158. sentry_sdk/integrations/trytond.py +15 -20
  159. sentry_sdk/integrations/typer.py +60 -0
  160. sentry_sdk/integrations/unleash.py +33 -0
  161. sentry_sdk/integrations/unraisablehook.py +53 -0
  162. sentry_sdk/integrations/wsgi.py +126 -125
  163. sentry_sdk/logger.py +96 -0
  164. sentry_sdk/metrics.py +81 -0
  165. sentry_sdk/monitor.py +120 -0
  166. sentry_sdk/profiler/__init__.py +49 -0
  167. sentry_sdk/profiler/continuous_profiler.py +730 -0
  168. sentry_sdk/profiler/transaction_profiler.py +839 -0
  169. sentry_sdk/profiler/utils.py +195 -0
  170. sentry_sdk/scope.py +1542 -112
  171. sentry_sdk/scrubber.py +177 -0
  172. sentry_sdk/serializer.py +152 -210
  173. sentry_sdk/session.py +177 -0
  174. sentry_sdk/sessions.py +202 -179
  175. sentry_sdk/spotlight.py +242 -0
  176. sentry_sdk/tracing.py +1202 -294
  177. sentry_sdk/tracing_utils.py +1236 -0
  178. sentry_sdk/transport.py +693 -189
  179. sentry_sdk/types.py +52 -0
  180. sentry_sdk/utils.py +1395 -228
  181. sentry_sdk/worker.py +30 -17
  182. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  183. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  184. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  185. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  186. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  187. sentry_sdk/_functools.py +0 -66
  188. sentry_sdk/integrations/celery.py +0 -275
  189. sentry_sdk/integrations/redis.py +0 -103
  190. sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.18.0.dist-info/METADATA +0 -66
  192. sentry_sdk-0.18.0.dist-info/RECORD +0 -65
  193. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,165 @@
1
+ import inspect
2
+ import functools
3
+ import sys
4
+
5
+ import sentry_sdk
6
+ from sentry_sdk.consts import OP, SPANSTATUS
7
+ from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
8
+ from sentry_sdk.tracing import TransactionSource
9
+ from sentry_sdk.utils import (
10
+ event_from_exception,
11
+ logger,
12
+ package_version,
13
+ qualname_from_function,
14
+ reraise,
15
+ )
16
+
17
+ try:
18
+ import ray # type: ignore[import-not-found]
19
+ except ImportError:
20
+ raise DidNotEnable("Ray not installed.")
21
+
22
+ from typing import TYPE_CHECKING
23
+
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Callable
26
+ from typing import Any, Optional
27
+ from sentry_sdk.utils import ExcInfo
28
+
29
+
30
+ def _check_sentry_initialized():
31
+ # type: () -> None
32
+ if sentry_sdk.get_client().is_active():
33
+ return
34
+
35
+ logger.debug(
36
+ "[Tracing] Sentry not initialized in ray cluster worker, performance data will be discarded."
37
+ )
38
+
39
+
40
+ def _patch_ray_remote():
41
+ # type: () -> None
42
+ old_remote = ray.remote
43
+
44
+ @functools.wraps(old_remote)
45
+ def new_remote(f=None, *args, **kwargs):
46
+ # type: (Optional[Callable[..., Any]], *Any, **Any) -> Callable[..., Any]
47
+
48
+ if inspect.isclass(f):
49
+ # Ray Actors
50
+ # (https://docs.ray.io/en/latest/ray-core/actors.html)
51
+ # are not supported
52
+ # (Only Ray Tasks are supported)
53
+ return old_remote(f, *args, **kwargs)
54
+
55
+ def wrapper(user_f):
56
+ # type: (Callable[..., Any]) -> Any
57
+ @functools.wraps(user_f)
58
+ def new_func(*f_args, _sentry_tracing=None, **f_kwargs):
59
+ # type: (Any, Optional[dict[str, Any]], Any) -> Any
60
+ _check_sentry_initialized()
61
+
62
+ transaction = sentry_sdk.continue_trace(
63
+ _sentry_tracing or {},
64
+ op=OP.QUEUE_TASK_RAY,
65
+ name=qualname_from_function(user_f),
66
+ origin=RayIntegration.origin,
67
+ source=TransactionSource.TASK,
68
+ )
69
+
70
+ with sentry_sdk.start_transaction(transaction) as transaction:
71
+ try:
72
+ result = user_f(*f_args, **f_kwargs)
73
+ transaction.set_status(SPANSTATUS.OK)
74
+ except Exception:
75
+ transaction.set_status(SPANSTATUS.INTERNAL_ERROR)
76
+ exc_info = sys.exc_info()
77
+ _capture_exception(exc_info)
78
+ reraise(*exc_info)
79
+
80
+ return result
81
+
82
+ # Patching new_func signature to add the _sentry_tracing parameter to it
83
+ # Ray later inspects the signature and finds the unexpected parameter otherwise
84
+ signature = inspect.signature(new_func)
85
+ params = list(signature.parameters.values())
86
+ params.append(
87
+ inspect.Parameter(
88
+ "_sentry_tracing",
89
+ kind=inspect.Parameter.KEYWORD_ONLY,
90
+ default=None,
91
+ )
92
+ )
93
+ new_func.__signature__ = signature.replace(parameters=params) # type: ignore[attr-defined]
94
+
95
+ if f:
96
+ rv = old_remote(new_func)
97
+ else:
98
+ rv = old_remote(*args, **kwargs)(new_func)
99
+ old_remote_method = rv.remote
100
+
101
+ def _remote_method_with_header_propagation(*args, **kwargs):
102
+ # type: (*Any, **Any) -> Any
103
+ """
104
+ Ray Client
105
+ """
106
+ with sentry_sdk.start_span(
107
+ op=OP.QUEUE_SUBMIT_RAY,
108
+ name=qualname_from_function(user_f),
109
+ origin=RayIntegration.origin,
110
+ ) as span:
111
+ tracing = {
112
+ k: v
113
+ for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers()
114
+ }
115
+ try:
116
+ result = old_remote_method(
117
+ *args, **kwargs, _sentry_tracing=tracing
118
+ )
119
+ span.set_status(SPANSTATUS.OK)
120
+ except Exception:
121
+ span.set_status(SPANSTATUS.INTERNAL_ERROR)
122
+ exc_info = sys.exc_info()
123
+ _capture_exception(exc_info)
124
+ reraise(*exc_info)
125
+
126
+ return result
127
+
128
+ rv.remote = _remote_method_with_header_propagation
129
+
130
+ return rv
131
+
132
+ if f is not None:
133
+ return wrapper(f)
134
+ else:
135
+ return wrapper
136
+
137
+ ray.remote = new_remote
138
+
139
+
140
+ def _capture_exception(exc_info, **kwargs):
141
+ # type: (ExcInfo, **Any) -> None
142
+ client = sentry_sdk.get_client()
143
+
144
+ event, hint = event_from_exception(
145
+ exc_info,
146
+ client_options=client.options,
147
+ mechanism={
148
+ "handled": False,
149
+ "type": RayIntegration.identifier,
150
+ },
151
+ )
152
+ sentry_sdk.capture_event(event, hint=hint)
153
+
154
+
155
+ class RayIntegration(Integration):
156
+ identifier = "ray"
157
+ origin = f"auto.queue.{identifier}"
158
+
159
+ @staticmethod
160
+ def setup_once():
161
+ # type: () -> None
162
+ version = package_version("ray")
163
+ _check_minimum_version(RayIntegration, version)
164
+
165
+ _patch_ray_remote()
@@ -0,0 +1,48 @@
1
+ import warnings
2
+
3
+ from sentry_sdk.integrations import Integration, DidNotEnable
4
+ from sentry_sdk.integrations.redis.consts import _DEFAULT_MAX_DATA_SIZE
5
+ from sentry_sdk.integrations.redis.rb import _patch_rb
6
+ from sentry_sdk.integrations.redis.redis import _patch_redis
7
+ from sentry_sdk.integrations.redis.redis_cluster import _patch_redis_cluster
8
+ from sentry_sdk.integrations.redis.redis_py_cluster_legacy import _patch_rediscluster
9
+ from sentry_sdk.utils import logger
10
+
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from typing import Optional
15
+
16
+
17
+ class RedisIntegration(Integration):
18
+ identifier = "redis"
19
+
20
+ def __init__(self, max_data_size=_DEFAULT_MAX_DATA_SIZE, cache_prefixes=None):
21
+ # type: (Optional[int], Optional[list[str]]) -> None
22
+ self.max_data_size = max_data_size
23
+ self.cache_prefixes = cache_prefixes if cache_prefixes is not None else []
24
+
25
+ if max_data_size is not None:
26
+ warnings.warn(
27
+ "The `max_data_size` parameter of `RedisIntegration` is "
28
+ "deprecated and will be removed in version 3.0 of sentry-sdk.",
29
+ DeprecationWarning,
30
+ stacklevel=2,
31
+ )
32
+
33
+ @staticmethod
34
+ def setup_once():
35
+ # type: () -> None
36
+ try:
37
+ from redis import StrictRedis, client
38
+ except ImportError:
39
+ raise DidNotEnable("Redis client not installed")
40
+
41
+ _patch_redis(StrictRedis, client)
42
+ _patch_redis_cluster()
43
+ _patch_rb()
44
+
45
+ try:
46
+ _patch_rediscluster()
47
+ except Exception:
48
+ logger.exception("Error occurred while patching `rediscluster` library")
@@ -0,0 +1,116 @@
1
+ import sentry_sdk
2
+ from sentry_sdk.consts import OP
3
+ from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
4
+ from sentry_sdk.integrations.redis.modules.caches import (
5
+ _compile_cache_span_properties,
6
+ _set_cache_data,
7
+ )
8
+ from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
9
+ from sentry_sdk.integrations.redis.utils import (
10
+ _set_client_data,
11
+ _set_pipeline_data,
12
+ )
13
+ from sentry_sdk.tracing import Span
14
+ from sentry_sdk.utils import capture_internal_exceptions
15
+
16
+ from typing import TYPE_CHECKING
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Callable
20
+ from typing import Any, Union
21
+ from redis.asyncio.client import Pipeline, StrictRedis
22
+ from redis.asyncio.cluster import ClusterPipeline, RedisCluster
23
+
24
+
25
+ def patch_redis_async_pipeline(
26
+ pipeline_cls, is_cluster, get_command_args_fn, set_db_data_fn
27
+ ):
28
+ # type: (Union[type[Pipeline[Any]], type[ClusterPipeline[Any]]], bool, Any, Callable[[Span, Any], None]) -> None
29
+ old_execute = pipeline_cls.execute
30
+
31
+ from sentry_sdk.integrations.redis import RedisIntegration
32
+
33
+ async def _sentry_execute(self, *args, **kwargs):
34
+ # type: (Any, *Any, **Any) -> Any
35
+ if sentry_sdk.get_client().get_integration(RedisIntegration) is None:
36
+ return await old_execute(self, *args, **kwargs)
37
+
38
+ with sentry_sdk.start_span(
39
+ op=OP.DB_REDIS,
40
+ name="redis.pipeline.execute",
41
+ origin=SPAN_ORIGIN,
42
+ ) as span:
43
+ with capture_internal_exceptions():
44
+ try:
45
+ command_seq = self._execution_strategy._command_queue
46
+ except AttributeError:
47
+ if is_cluster:
48
+ command_seq = self._command_stack
49
+ else:
50
+ command_seq = self.command_stack
51
+
52
+ set_db_data_fn(span, self)
53
+ _set_pipeline_data(
54
+ span,
55
+ is_cluster,
56
+ get_command_args_fn,
57
+ False if is_cluster else self.is_transaction,
58
+ command_seq,
59
+ )
60
+
61
+ return await old_execute(self, *args, **kwargs)
62
+
63
+ pipeline_cls.execute = _sentry_execute # type: ignore
64
+
65
+
66
+ def patch_redis_async_client(cls, is_cluster, set_db_data_fn):
67
+ # type: (Union[type[StrictRedis[Any]], type[RedisCluster[Any]]], bool, Callable[[Span, Any], None]) -> None
68
+ old_execute_command = cls.execute_command
69
+
70
+ from sentry_sdk.integrations.redis import RedisIntegration
71
+
72
+ async def _sentry_execute_command(self, name, *args, **kwargs):
73
+ # type: (Any, str, *Any, **Any) -> Any
74
+ integration = sentry_sdk.get_client().get_integration(RedisIntegration)
75
+ if integration is None:
76
+ return await old_execute_command(self, name, *args, **kwargs)
77
+
78
+ cache_properties = _compile_cache_span_properties(
79
+ name,
80
+ args,
81
+ kwargs,
82
+ integration,
83
+ )
84
+
85
+ cache_span = None
86
+ if cache_properties["is_cache_key"] and cache_properties["op"] is not None:
87
+ cache_span = sentry_sdk.start_span(
88
+ op=cache_properties["op"],
89
+ name=cache_properties["description"],
90
+ origin=SPAN_ORIGIN,
91
+ )
92
+ cache_span.__enter__()
93
+
94
+ db_properties = _compile_db_span_properties(integration, name, args)
95
+
96
+ db_span = sentry_sdk.start_span(
97
+ op=db_properties["op"],
98
+ name=db_properties["description"],
99
+ origin=SPAN_ORIGIN,
100
+ )
101
+ db_span.__enter__()
102
+
103
+ set_db_data_fn(db_span, self)
104
+ _set_client_data(db_span, is_cluster, name, *args)
105
+
106
+ value = await old_execute_command(self, name, *args, **kwargs)
107
+
108
+ db_span.__exit__(None, None, None)
109
+
110
+ if cache_span:
111
+ _set_cache_data(cache_span, self, cache_properties, value)
112
+ cache_span.__exit__(None, None, None)
113
+
114
+ return value
115
+
116
+ cls.execute_command = _sentry_execute_command # type: ignore
@@ -0,0 +1,119 @@
1
+ import sentry_sdk
2
+ from sentry_sdk.consts import OP
3
+ from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
4
+ from sentry_sdk.integrations.redis.modules.caches import (
5
+ _compile_cache_span_properties,
6
+ _set_cache_data,
7
+ )
8
+ from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
9
+ from sentry_sdk.integrations.redis.utils import (
10
+ _set_client_data,
11
+ _set_pipeline_data,
12
+ )
13
+ from sentry_sdk.tracing import Span
14
+ from sentry_sdk.utils import capture_internal_exceptions
15
+
16
+ from typing import TYPE_CHECKING
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Callable
20
+ from typing import Any
21
+
22
+
23
+ def patch_redis_pipeline(
24
+ pipeline_cls,
25
+ is_cluster,
26
+ get_command_args_fn,
27
+ set_db_data_fn,
28
+ ):
29
+ # type: (Any, bool, Any, Callable[[Span, Any], None]) -> None
30
+ old_execute = pipeline_cls.execute
31
+
32
+ from sentry_sdk.integrations.redis import RedisIntegration
33
+
34
+ def sentry_patched_execute(self, *args, **kwargs):
35
+ # type: (Any, *Any, **Any) -> Any
36
+ if sentry_sdk.get_client().get_integration(RedisIntegration) is None:
37
+ return old_execute(self, *args, **kwargs)
38
+
39
+ with sentry_sdk.start_span(
40
+ op=OP.DB_REDIS,
41
+ name="redis.pipeline.execute",
42
+ origin=SPAN_ORIGIN,
43
+ ) as span:
44
+ with capture_internal_exceptions():
45
+ command_seq = None
46
+ try:
47
+ command_seq = self._execution_strategy.command_queue
48
+ except AttributeError:
49
+ command_seq = self.command_stack
50
+
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,
58
+ )
59
+
60
+ return old_execute(self, *args, **kwargs)
61
+
62
+ pipeline_cls.execute = sentry_patched_execute
63
+
64
+
65
+ def patch_redis_client(cls, is_cluster, set_db_data_fn):
66
+ # type: (Any, bool, Callable[[Span, Any], None]) -> None
67
+ """
68
+ This function can be used to instrument custom redis client classes or
69
+ subclasses.
70
+ """
71
+ old_execute_command = cls.execute_command
72
+
73
+ from sentry_sdk.integrations.redis import RedisIntegration
74
+
75
+ def sentry_patched_execute_command(self, name, *args, **kwargs):
76
+ # type: (Any, str, *Any, **Any) -> Any
77
+ integration = sentry_sdk.get_client().get_integration(RedisIntegration)
78
+ if integration is None:
79
+ return old_execute_command(self, name, *args, **kwargs)
80
+
81
+ cache_properties = _compile_cache_span_properties(
82
+ name,
83
+ args,
84
+ kwargs,
85
+ integration,
86
+ )
87
+
88
+ cache_span = None
89
+ if cache_properties["is_cache_key"] and cache_properties["op"] is not None:
90
+ cache_span = sentry_sdk.start_span(
91
+ op=cache_properties["op"],
92
+ name=cache_properties["description"],
93
+ origin=SPAN_ORIGIN,
94
+ )
95
+ cache_span.__enter__()
96
+
97
+ db_properties = _compile_db_span_properties(integration, name, args)
98
+
99
+ db_span = sentry_sdk.start_span(
100
+ op=db_properties["op"],
101
+ name=db_properties["description"],
102
+ origin=SPAN_ORIGIN,
103
+ )
104
+ db_span.__enter__()
105
+
106
+ set_db_data_fn(db_span, self)
107
+ _set_client_data(db_span, is_cluster, name, *args)
108
+
109
+ value = old_execute_command(self, name, *args, **kwargs)
110
+
111
+ db_span.__exit__(None, None, None)
112
+
113
+ if cache_span:
114
+ _set_cache_data(cache_span, self, cache_properties, value)
115
+ cache_span.__exit__(None, None, None)
116
+
117
+ return value
118
+
119
+ cls.execute_command = sentry_patched_execute_command
@@ -0,0 +1,19 @@
1
+ SPAN_ORIGIN = "auto.db.redis"
2
+
3
+ _SINGLE_KEY_COMMANDS = frozenset(
4
+ ["decr", "decrby", "get", "incr", "incrby", "pttl", "set", "setex", "setnx", "ttl"],
5
+ )
6
+ _MULTI_KEY_COMMANDS = frozenset(
7
+ [
8
+ "del",
9
+ "touch",
10
+ "unlink",
11
+ "mget",
12
+ ],
13
+ )
14
+ _COMMANDS_INCLUDING_SENSITIVE_DATA = [
15
+ "auth",
16
+ ]
17
+ _MAX_NUM_ARGS = 10 # Trim argument lists to this many values
18
+ _MAX_NUM_COMMANDS = 10 # Trim command lists to this many values
19
+ _DEFAULT_MAX_DATA_SIZE = None
File without changes
@@ -0,0 +1,118 @@
1
+ """
2
+ Code used for the Caches module in Sentry
3
+ """
4
+
5
+ from sentry_sdk.consts import OP, SPANDATA
6
+ from sentry_sdk.integrations.redis.utils import _get_safe_key, _key_as_string
7
+ from sentry_sdk.utils import capture_internal_exceptions
8
+
9
+ GET_COMMANDS = ("get", "mget")
10
+ SET_COMMANDS = ("set", "setex")
11
+
12
+ from typing import TYPE_CHECKING
13
+
14
+ if TYPE_CHECKING:
15
+ from sentry_sdk.integrations.redis import RedisIntegration
16
+ from sentry_sdk.tracing import Span
17
+ from typing import Any, Optional
18
+
19
+
20
+ def _get_op(name):
21
+ # type: (str) -> Optional[str]
22
+ op = None
23
+ if name.lower() in GET_COMMANDS:
24
+ op = OP.CACHE_GET
25
+ elif name.lower() in SET_COMMANDS:
26
+ op = OP.CACHE_PUT
27
+
28
+ return op
29
+
30
+
31
+ def _compile_cache_span_properties(redis_command, args, kwargs, integration):
32
+ # type: (str, tuple[Any, ...], dict[str, Any], RedisIntegration) -> dict[str, Any]
33
+ key = _get_safe_key(redis_command, args, kwargs)
34
+ key_as_string = _key_as_string(key)
35
+ keys_as_string = key_as_string.split(", ")
36
+
37
+ is_cache_key = False
38
+ for prefix in integration.cache_prefixes:
39
+ for kee in keys_as_string:
40
+ if kee.startswith(prefix):
41
+ is_cache_key = True
42
+ break
43
+ if is_cache_key:
44
+ break
45
+
46
+ value = None
47
+ if redis_command.lower() in SET_COMMANDS:
48
+ value = args[-1]
49
+
50
+ properties = {
51
+ "op": _get_op(redis_command),
52
+ "description": _get_cache_span_description(
53
+ redis_command, args, kwargs, integration
54
+ ),
55
+ "key": key,
56
+ "key_as_string": key_as_string,
57
+ "redis_command": redis_command.lower(),
58
+ "is_cache_key": is_cache_key,
59
+ "value": value,
60
+ }
61
+
62
+ return properties
63
+
64
+
65
+ def _get_cache_span_description(redis_command, args, kwargs, integration):
66
+ # type: (str, tuple[Any, ...], dict[str, Any], RedisIntegration) -> str
67
+ description = _key_as_string(_get_safe_key(redis_command, args, kwargs))
68
+
69
+ if integration.max_data_size and len(description) > integration.max_data_size:
70
+ description = description[: integration.max_data_size - len("...")] + "..."
71
+
72
+ return description
73
+
74
+
75
+ def _set_cache_data(span, redis_client, properties, return_value):
76
+ # type: (Span, Any, dict[str, Any], Optional[Any]) -> None
77
+ with capture_internal_exceptions():
78
+ span.set_data(SPANDATA.CACHE_KEY, properties["key"])
79
+
80
+ if properties["redis_command"] in GET_COMMANDS:
81
+ if return_value is not None:
82
+ span.set_data(SPANDATA.CACHE_HIT, True)
83
+ size = (
84
+ len(str(return_value).encode("utf-8"))
85
+ if not isinstance(return_value, bytes)
86
+ else len(return_value)
87
+ )
88
+ span.set_data(SPANDATA.CACHE_ITEM_SIZE, size)
89
+ else:
90
+ span.set_data(SPANDATA.CACHE_HIT, False)
91
+
92
+ elif properties["redis_command"] in SET_COMMANDS:
93
+ if properties["value"] is not None:
94
+ size = (
95
+ len(properties["value"].encode("utf-8"))
96
+ if not isinstance(properties["value"], bytes)
97
+ else len(properties["value"])
98
+ )
99
+ span.set_data(SPANDATA.CACHE_ITEM_SIZE, size)
100
+
101
+ try:
102
+ connection_params = redis_client.connection_pool.connection_kwargs
103
+ except AttributeError:
104
+ # If it is a cluster, there is no connection_pool attribute so we
105
+ # need to get the default node from the cluster instance
106
+ default_node = redis_client.get_default_node()
107
+ connection_params = {
108
+ "host": default_node.host,
109
+ "port": default_node.port,
110
+ }
111
+
112
+ host = connection_params.get("host")
113
+ if host is not None:
114
+ span.set_data(SPANDATA.NETWORK_PEER_ADDRESS, host)
115
+
116
+ port = connection_params.get("port")
117
+ if port is not None:
118
+ span.set_data(SPANDATA.NETWORK_PEER_PORT, port)
@@ -0,0 +1,65 @@
1
+ """
2
+ Code used for the Queries module in Sentry
3
+ """
4
+
5
+ from sentry_sdk.consts import OP, SPANDATA
6
+ from sentry_sdk.integrations.redis.utils import _get_safe_command
7
+ from sentry_sdk.utils import capture_internal_exceptions
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from redis import Redis
13
+ from sentry_sdk.integrations.redis import RedisIntegration
14
+ from sentry_sdk.tracing import Span
15
+ from typing import Any
16
+
17
+
18
+ def _compile_db_span_properties(integration, redis_command, args):
19
+ # type: (RedisIntegration, str, tuple[Any, ...]) -> dict[str, Any]
20
+ description = _get_db_span_description(integration, redis_command, args)
21
+
22
+ properties = {
23
+ "op": OP.DB_REDIS,
24
+ "description": description,
25
+ }
26
+
27
+ return properties
28
+
29
+
30
+ def _get_db_span_description(integration, command_name, args):
31
+ # type: (RedisIntegration, str, tuple[Any, ...]) -> str
32
+ description = command_name
33
+
34
+ with capture_internal_exceptions():
35
+ description = _get_safe_command(command_name, args)
36
+
37
+ if integration.max_data_size and len(description) > integration.max_data_size:
38
+ description = description[: integration.max_data_size - len("...")] + "..."
39
+
40
+ return description
41
+
42
+
43
+ def _set_db_data_on_span(span, connection_params):
44
+ # type: (Span, dict[str, Any]) -> None
45
+ span.set_data(SPANDATA.DB_SYSTEM, "redis")
46
+
47
+ db = connection_params.get("db")
48
+ if db is not None:
49
+ span.set_data(SPANDATA.DB_NAME, str(db))
50
+
51
+ host = connection_params.get("host")
52
+ if host is not None:
53
+ span.set_data(SPANDATA.SERVER_ADDRESS, host)
54
+
55
+ port = connection_params.get("port")
56
+ if port is not None:
57
+ span.set_data(SPANDATA.SERVER_PORT, port)
58
+
59
+
60
+ def _set_db_data(span, redis_instance):
61
+ # type: (Span, Redis[Any]) -> None
62
+ try:
63
+ _set_db_data_on_span(span, redis_instance.connection_pool.connection_kwargs)
64
+ except AttributeError:
65
+ pass # connections_kwargs may be missing in some cases
@@ -0,0 +1,32 @@
1
+ """
2
+ Instrumentation for Redis Blaster (rb)
3
+
4
+ https://github.com/getsentry/rb
5
+ """
6
+
7
+ from sentry_sdk.integrations.redis._sync_common import patch_redis_client
8
+ from sentry_sdk.integrations.redis.modules.queries import _set_db_data
9
+
10
+
11
+ def _patch_rb():
12
+ # type: () -> None
13
+ try:
14
+ import rb.clients # type: ignore
15
+ except ImportError:
16
+ pass
17
+ else:
18
+ patch_redis_client(
19
+ rb.clients.FanoutClient,
20
+ is_cluster=False,
21
+ set_db_data_fn=_set_db_data,
22
+ )
23
+ patch_redis_client(
24
+ rb.clients.MappingClient,
25
+ is_cluster=False,
26
+ set_db_data_fn=_set_db_data,
27
+ )
28
+ patch_redis_client(
29
+ rb.clients.RoutingClient,
30
+ is_cluster=False,
31
+ set_db_data_fn=_set_db_data,
32
+ )