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.
- sentry_sdk/__init__.py +48 -6
- sentry_sdk/_compat.py +64 -56
- sentry_sdk/_init_implementation.py +84 -0
- sentry_sdk/_log_batcher.py +172 -0
- sentry_sdk/_lru_cache.py +47 -0
- sentry_sdk/_metrics_batcher.py +167 -0
- sentry_sdk/_queue.py +81 -19
- sentry_sdk/_types.py +311 -11
- sentry_sdk/_werkzeug.py +98 -0
- sentry_sdk/ai/__init__.py +7 -0
- sentry_sdk/ai/monitoring.py +137 -0
- sentry_sdk/ai/utils.py +144 -0
- sentry_sdk/api.py +409 -67
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +849 -103
- sentry_sdk/consts.py +1389 -34
- sentry_sdk/crons/__init__.py +10 -0
- sentry_sdk/crons/api.py +62 -0
- sentry_sdk/crons/consts.py +4 -0
- sentry_sdk/crons/decorator.py +135 -0
- sentry_sdk/debug.py +12 -15
- sentry_sdk/envelope.py +112 -61
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +442 -386
- sentry_sdk/integrations/__init__.py +228 -58
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +131 -40
- sentry_sdk/integrations/aiohttp.py +221 -72
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +4 -6
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +237 -135
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +13 -18
- sentry_sdk/integrations/aws_lambda.py +233 -80
- sentry_sdk/integrations/beam.py +27 -35
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +91 -69
- sentry_sdk/integrations/celery/__init__.py +529 -0
- sentry_sdk/integrations/celery/beat.py +293 -0
- sentry_sdk/integrations/celery/utils.py +43 -0
- sentry_sdk/integrations/chalice.py +35 -28
- sentry_sdk/integrations/clickhouse_driver.py +177 -0
- sentry_sdk/integrations/cloud_resource_context.py +280 -0
- sentry_sdk/integrations/cohere.py +274 -0
- sentry_sdk/integrations/dedupe.py +32 -8
- sentry_sdk/integrations/django/__init__.py +343 -89
- sentry_sdk/integrations/django/asgi.py +201 -22
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +80 -32
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +69 -2
- sentry_sdk/integrations/django/transactions.py +39 -14
- sentry_sdk/integrations/django/views.py +69 -16
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +19 -13
- sentry_sdk/integrations/executing.py +5 -6
- sentry_sdk/integrations/falcon.py +128 -65
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +114 -75
- sentry_sdk/integrations/gcp.py +67 -36
- sentry_sdk/integrations/gnu_backtrace.py +14 -22
- sentry_sdk/integrations/google_genai/__init__.py +301 -0
- sentry_sdk/integrations/google_genai/consts.py +16 -0
- sentry_sdk/integrations/google_genai/streaming.py +155 -0
- sentry_sdk/integrations/google_genai/utils.py +576 -0
- sentry_sdk/integrations/gql.py +162 -0
- sentry_sdk/integrations/graphene.py +151 -0
- sentry_sdk/integrations/grpc/__init__.py +168 -0
- sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
- sentry_sdk/integrations/grpc/aio/client.py +95 -0
- sentry_sdk/integrations/grpc/aio/server.py +100 -0
- sentry_sdk/integrations/grpc/client.py +91 -0
- sentry_sdk/integrations/grpc/consts.py +1 -0
- sentry_sdk/integrations/grpc/server.py +66 -0
- sentry_sdk/integrations/httpx.py +178 -0
- sentry_sdk/integrations/huey.py +174 -0
- sentry_sdk/integrations/huggingface_hub.py +378 -0
- sentry_sdk/integrations/langchain.py +1132 -0
- sentry_sdk/integrations/langgraph.py +337 -0
- sentry_sdk/integrations/launchdarkly.py +61 -0
- sentry_sdk/integrations/litellm.py +287 -0
- sentry_sdk/integrations/litestar.py +315 -0
- sentry_sdk/integrations/logging.py +261 -85
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +6 -33
- sentry_sdk/integrations/openai.py +725 -0
- sentry_sdk/integrations/openai_agents/__init__.py +61 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
- sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
- sentry_sdk/integrations/openai_agents/utils.py +199 -0
- sentry_sdk/integrations/openfeature.py +35 -0
- sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
- sentry_sdk/integrations/opentelemetry/consts.py +5 -0
- sentry_sdk/integrations/opentelemetry/integration.py +58 -0
- sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
- sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
- sentry_sdk/integrations/otlp.py +82 -0
- sentry_sdk/integrations/pure_eval.py +20 -11
- sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
- sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
- sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
- sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
- sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
- sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
- sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
- sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
- sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
- sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
- sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
- sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
- sentry_sdk/integrations/pymongo.py +214 -0
- sentry_sdk/integrations/pyramid.py +71 -60
- sentry_sdk/integrations/quart.py +237 -0
- sentry_sdk/integrations/ray.py +165 -0
- sentry_sdk/integrations/redis/__init__.py +48 -0
- sentry_sdk/integrations/redis/_async_common.py +116 -0
- sentry_sdk/integrations/redis/_sync_common.py +119 -0
- sentry_sdk/integrations/redis/consts.py +19 -0
- sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- sentry_sdk/integrations/redis/modules/caches.py +118 -0
- sentry_sdk/integrations/redis/modules/queries.py +65 -0
- sentry_sdk/integrations/redis/rb.py +32 -0
- sentry_sdk/integrations/redis/redis.py +69 -0
- sentry_sdk/integrations/redis/redis_cluster.py +107 -0
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
- sentry_sdk/integrations/redis/utils.py +148 -0
- sentry_sdk/integrations/rq.py +62 -52
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +248 -114
- sentry_sdk/integrations/serverless.py +13 -22
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/spark_driver.py +115 -62
- sentry_sdk/integrations/spark/spark_worker.py +42 -50
- sentry_sdk/integrations/sqlalchemy.py +82 -37
- sentry_sdk/integrations/starlette.py +737 -0
- sentry_sdk/integrations/starlite.py +292 -0
- sentry_sdk/integrations/statsig.py +37 -0
- sentry_sdk/integrations/stdlib.py +100 -58
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +142 -38
- sentry_sdk/integrations/tornado.py +68 -53
- sentry_sdk/integrations/trytond.py +15 -20
- sentry_sdk/integrations/typer.py +60 -0
- sentry_sdk/integrations/unleash.py +33 -0
- sentry_sdk/integrations/unraisablehook.py +53 -0
- sentry_sdk/integrations/wsgi.py +126 -125
- sentry_sdk/logger.py +96 -0
- sentry_sdk/metrics.py +81 -0
- sentry_sdk/monitor.py +120 -0
- sentry_sdk/profiler/__init__.py +49 -0
- sentry_sdk/profiler/continuous_profiler.py +730 -0
- sentry_sdk/profiler/transaction_profiler.py +839 -0
- sentry_sdk/profiler/utils.py +195 -0
- sentry_sdk/scope.py +1542 -112
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +152 -210
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +202 -179
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1202 -294
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +693 -189
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1395 -228
- sentry_sdk/worker.py +30 -17
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
- sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
- sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
- sentry_sdk/_functools.py +0 -66
- sentry_sdk/integrations/celery.py +0 -275
- sentry_sdk/integrations/redis.py +0 -103
- sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
- sentry_sdk-0.18.0.dist-info/METADATA +0 -66
- sentry_sdk-0.18.0.dist-info/RECORD +0 -65
- {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
|
+
)
|