sentry-sdk 0.7.5__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 -30
- sentry_sdk/_compat.py +74 -61
- 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 +289 -0
- sentry_sdk/_types.py +338 -0
- 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 +496 -80
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +1023 -103
- sentry_sdk/consts.py +1438 -66
- 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 +15 -14
- sentry_sdk/envelope.py +369 -0
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +611 -280
- sentry_sdk/integrations/__init__.py +276 -49
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +180 -44
- sentry_sdk/integrations/aiohttp.py +291 -42
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +9 -8
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +341 -0
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +17 -10
- sentry_sdk/integrations/aws_lambda.py +377 -62
- sentry_sdk/integrations/beam.py +176 -0
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +221 -0
- 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 +134 -0
- 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 +48 -14
- sentry_sdk/integrations/django/__init__.py +584 -191
- sentry_sdk/integrations/django/asgi.py +245 -0
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +187 -0
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +79 -5
- sentry_sdk/integrations/django/transactions.py +49 -22
- sentry_sdk/integrations/django/views.py +96 -0
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +50 -13
- sentry_sdk/integrations/executing.py +67 -0
- sentry_sdk/integrations/falcon.py +272 -0
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +142 -88
- sentry_sdk/integrations/gcp.py +239 -0
- sentry_sdk/integrations/gnu_backtrace.py +99 -0
- 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 +307 -96
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +14 -31
- 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 +141 -0
- 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 +112 -68
- 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 +95 -37
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +294 -123
- sentry_sdk/integrations/serverless.py +48 -19
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/__init__.py +4 -0
- sentry_sdk/integrations/spark/spark_driver.py +316 -0
- sentry_sdk/integrations/spark/spark_worker.py +116 -0
- sentry_sdk/integrations/sqlalchemy.py +142 -0
- 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 +235 -29
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +158 -28
- sentry_sdk/integrations/tornado.py +84 -52
- sentry_sdk/integrations/trytond.py +50 -0
- 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 +201 -119
- 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/py.typed +0 -0
- sentry_sdk/scope.py +1713 -85
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +405 -0
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +275 -0
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1486 -0
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +806 -134
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1625 -465
- sentry_sdk/worker.py +54 -25
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.7.5.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/integrations/celery.py +0 -119
- sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
- sentry_sdk-0.7.5.dist-info/METADATA +0 -36
- sentry_sdk-0.7.5.dist-info/RECORD +0 -39
- {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Instrumentation for Redis
|
|
3
|
+
|
|
4
|
+
https://github.com/redis/redis-py
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from sentry_sdk.integrations.redis._sync_common import (
|
|
8
|
+
patch_redis_client,
|
|
9
|
+
patch_redis_pipeline,
|
|
10
|
+
)
|
|
11
|
+
from sentry_sdk.integrations.redis.modules.queries import _set_db_data
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from typing import Any, Sequence
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_redis_command_args(command):
|
|
20
|
+
# type: (Any) -> Sequence[Any]
|
|
21
|
+
return command[0]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _patch_redis(StrictRedis, client): # noqa: N803
|
|
25
|
+
# type: (Any, Any) -> None
|
|
26
|
+
patch_redis_client(
|
|
27
|
+
StrictRedis,
|
|
28
|
+
is_cluster=False,
|
|
29
|
+
set_db_data_fn=_set_db_data,
|
|
30
|
+
)
|
|
31
|
+
patch_redis_pipeline(
|
|
32
|
+
client.Pipeline,
|
|
33
|
+
is_cluster=False,
|
|
34
|
+
get_command_args_fn=_get_redis_command_args,
|
|
35
|
+
set_db_data_fn=_set_db_data,
|
|
36
|
+
)
|
|
37
|
+
try:
|
|
38
|
+
strict_pipeline = client.StrictPipeline
|
|
39
|
+
except AttributeError:
|
|
40
|
+
pass
|
|
41
|
+
else:
|
|
42
|
+
patch_redis_pipeline(
|
|
43
|
+
strict_pipeline,
|
|
44
|
+
is_cluster=False,
|
|
45
|
+
get_command_args_fn=_get_redis_command_args,
|
|
46
|
+
set_db_data_fn=_set_db_data,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
import redis.asyncio
|
|
51
|
+
except ImportError:
|
|
52
|
+
pass
|
|
53
|
+
else:
|
|
54
|
+
from sentry_sdk.integrations.redis._async_common import (
|
|
55
|
+
patch_redis_async_client,
|
|
56
|
+
patch_redis_async_pipeline,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
patch_redis_async_client(
|
|
60
|
+
redis.asyncio.client.StrictRedis,
|
|
61
|
+
is_cluster=False,
|
|
62
|
+
set_db_data_fn=_set_db_data,
|
|
63
|
+
)
|
|
64
|
+
patch_redis_async_pipeline(
|
|
65
|
+
redis.asyncio.client.Pipeline,
|
|
66
|
+
False,
|
|
67
|
+
_get_redis_command_args,
|
|
68
|
+
set_db_data_fn=_set_db_data,
|
|
69
|
+
)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Instrumentation for RedisCluster
|
|
3
|
+
This is part of the main redis-py client.
|
|
4
|
+
|
|
5
|
+
https://github.com/redis/redis-py/blob/master/redis/cluster.py
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from sentry_sdk.integrations.redis._sync_common import (
|
|
9
|
+
patch_redis_client,
|
|
10
|
+
patch_redis_pipeline,
|
|
11
|
+
)
|
|
12
|
+
from sentry_sdk.integrations.redis.modules.queries import _set_db_data_on_span
|
|
13
|
+
from sentry_sdk.integrations.redis.utils import _parse_rediscluster_command
|
|
14
|
+
|
|
15
|
+
from sentry_sdk.utils import capture_internal_exceptions
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from typing import Any
|
|
21
|
+
from redis import RedisCluster
|
|
22
|
+
from redis.asyncio.cluster import (
|
|
23
|
+
RedisCluster as AsyncRedisCluster,
|
|
24
|
+
ClusterPipeline as AsyncClusterPipeline,
|
|
25
|
+
)
|
|
26
|
+
from sentry_sdk.tracing import Span
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _set_async_cluster_db_data(span, async_redis_cluster_instance):
|
|
30
|
+
# type: (Span, AsyncRedisCluster[Any]) -> None
|
|
31
|
+
default_node = async_redis_cluster_instance.get_default_node()
|
|
32
|
+
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)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _set_async_cluster_pipeline_db_data(span, async_redis_cluster_pipeline_instance):
|
|
37
|
+
# type: (Span, AsyncClusterPipeline[Any]) -> None
|
|
38
|
+
with capture_internal_exceptions():
|
|
39
|
+
client = getattr(async_redis_cluster_pipeline_instance, "cluster_client", None)
|
|
40
|
+
if client is None:
|
|
41
|
+
# In older redis-py versions, the AsyncClusterPipeline had a `_client`
|
|
42
|
+
# attr but it is private so potentially problematic and mypy does not
|
|
43
|
+
# recognize it - see
|
|
44
|
+
# https://github.com/redis/redis-py/blame/v5.0.0/redis/asyncio/cluster.py#L1386
|
|
45
|
+
client = (
|
|
46
|
+
async_redis_cluster_pipeline_instance._client # type: ignore[attr-defined]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
_set_async_cluster_db_data(
|
|
50
|
+
span,
|
|
51
|
+
client,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _set_cluster_db_data(span, redis_cluster_instance):
|
|
56
|
+
# type: (Span, RedisCluster[Any]) -> None
|
|
57
|
+
default_node = redis_cluster_instance.get_default_node()
|
|
58
|
+
|
|
59
|
+
if default_node is not None:
|
|
60
|
+
connection_params = {
|
|
61
|
+
"host": default_node.host,
|
|
62
|
+
"port": default_node.port,
|
|
63
|
+
}
|
|
64
|
+
_set_db_data_on_span(span, connection_params)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _patch_redis_cluster():
|
|
68
|
+
# type: () -> None
|
|
69
|
+
"""Patches the cluster module on redis SDK (as opposed to rediscluster library)"""
|
|
70
|
+
try:
|
|
71
|
+
from redis import RedisCluster, cluster
|
|
72
|
+
except ImportError:
|
|
73
|
+
pass
|
|
74
|
+
else:
|
|
75
|
+
patch_redis_client(
|
|
76
|
+
RedisCluster,
|
|
77
|
+
is_cluster=True,
|
|
78
|
+
set_db_data_fn=_set_cluster_db_data,
|
|
79
|
+
)
|
|
80
|
+
patch_redis_pipeline(
|
|
81
|
+
cluster.ClusterPipeline,
|
|
82
|
+
is_cluster=True,
|
|
83
|
+
get_command_args_fn=_parse_rediscluster_command,
|
|
84
|
+
set_db_data_fn=_set_cluster_db_data,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
from redis.asyncio import cluster as async_cluster
|
|
89
|
+
except ImportError:
|
|
90
|
+
pass
|
|
91
|
+
else:
|
|
92
|
+
from sentry_sdk.integrations.redis._async_common import (
|
|
93
|
+
patch_redis_async_client,
|
|
94
|
+
patch_redis_async_pipeline,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
patch_redis_async_client(
|
|
98
|
+
async_cluster.RedisCluster,
|
|
99
|
+
is_cluster=True,
|
|
100
|
+
set_db_data_fn=_set_async_cluster_db_data,
|
|
101
|
+
)
|
|
102
|
+
patch_redis_async_pipeline(
|
|
103
|
+
async_cluster.ClusterPipeline,
|
|
104
|
+
is_cluster=True,
|
|
105
|
+
get_command_args_fn=_parse_rediscluster_command,
|
|
106
|
+
set_db_data_fn=_set_async_cluster_pipeline_db_data,
|
|
107
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Instrumentation for redis-py-cluster
|
|
3
|
+
The project redis-py-cluster is EOL and was integrated into redis-py starting from version 4.1.0 (Dec 26, 2021).
|
|
4
|
+
|
|
5
|
+
https://github.com/grokzen/redis-py-cluster
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from sentry_sdk.integrations.redis._sync_common import (
|
|
9
|
+
patch_redis_client,
|
|
10
|
+
patch_redis_pipeline,
|
|
11
|
+
)
|
|
12
|
+
from sentry_sdk.integrations.redis.modules.queries import _set_db_data
|
|
13
|
+
from sentry_sdk.integrations.redis.utils import _parse_rediscluster_command
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _patch_rediscluster():
|
|
17
|
+
# type: () -> None
|
|
18
|
+
try:
|
|
19
|
+
import rediscluster # type: ignore
|
|
20
|
+
except ImportError:
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
patch_redis_client(
|
|
24
|
+
rediscluster.RedisCluster,
|
|
25
|
+
is_cluster=True,
|
|
26
|
+
set_db_data_fn=_set_db_data,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# up to v1.3.6, __version__ attribute is a tuple
|
|
30
|
+
# from v2.0.0, __version__ is a string and VERSION a tuple
|
|
31
|
+
version = getattr(rediscluster, "VERSION", rediscluster.__version__)
|
|
32
|
+
|
|
33
|
+
# StrictRedisCluster was introduced in v0.2.0 and removed in v2.0.0
|
|
34
|
+
# https://github.com/Grokzen/redis-py-cluster/blob/master/docs/release-notes.rst
|
|
35
|
+
if (0, 2, 0) < version < (2, 0, 0):
|
|
36
|
+
pipeline_cls = rediscluster.pipeline.StrictClusterPipeline
|
|
37
|
+
patch_redis_client(
|
|
38
|
+
rediscluster.StrictRedisCluster,
|
|
39
|
+
is_cluster=True,
|
|
40
|
+
set_db_data_fn=_set_db_data,
|
|
41
|
+
)
|
|
42
|
+
else:
|
|
43
|
+
pipeline_cls = rediscluster.pipeline.ClusterPipeline
|
|
44
|
+
|
|
45
|
+
patch_redis_pipeline(
|
|
46
|
+
pipeline_cls,
|
|
47
|
+
is_cluster=True,
|
|
48
|
+
get_command_args_fn=_parse_rediscluster_command,
|
|
49
|
+
set_db_data_fn=_set_db_data,
|
|
50
|
+
)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from sentry_sdk.consts import SPANDATA
|
|
2
|
+
from sentry_sdk.integrations.redis.consts import (
|
|
3
|
+
_COMMANDS_INCLUDING_SENSITIVE_DATA,
|
|
4
|
+
_MAX_NUM_ARGS,
|
|
5
|
+
_MAX_NUM_COMMANDS,
|
|
6
|
+
_MULTI_KEY_COMMANDS,
|
|
7
|
+
_SINGLE_KEY_COMMANDS,
|
|
8
|
+
)
|
|
9
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
10
|
+
from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Any, Optional, Sequence
|
|
16
|
+
from sentry_sdk.tracing import Span
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_safe_command(name, args):
|
|
20
|
+
# type: (str, Sequence[Any]) -> str
|
|
21
|
+
command_parts = [name]
|
|
22
|
+
|
|
23
|
+
name_low = name.lower()
|
|
24
|
+
send_default_pii = should_send_default_pii()
|
|
25
|
+
|
|
26
|
+
for i, arg in enumerate(args):
|
|
27
|
+
if i > _MAX_NUM_ARGS:
|
|
28
|
+
break
|
|
29
|
+
|
|
30
|
+
if name_low in _COMMANDS_INCLUDING_SENSITIVE_DATA:
|
|
31
|
+
command_parts.append(SENSITIVE_DATA_SUBSTITUTE)
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
arg_is_the_key = i == 0
|
|
35
|
+
if arg_is_the_key:
|
|
36
|
+
command_parts.append(repr(arg))
|
|
37
|
+
else:
|
|
38
|
+
if send_default_pii:
|
|
39
|
+
command_parts.append(repr(arg))
|
|
40
|
+
else:
|
|
41
|
+
command_parts.append(SENSITIVE_DATA_SUBSTITUTE)
|
|
42
|
+
|
|
43
|
+
command = " ".join(command_parts)
|
|
44
|
+
return command
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _safe_decode(key):
|
|
48
|
+
# type: (Any) -> str
|
|
49
|
+
if isinstance(key, bytes):
|
|
50
|
+
try:
|
|
51
|
+
return key.decode()
|
|
52
|
+
except UnicodeDecodeError:
|
|
53
|
+
return ""
|
|
54
|
+
|
|
55
|
+
return str(key)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _key_as_string(key):
|
|
59
|
+
# type: (Any) -> str
|
|
60
|
+
if isinstance(key, (dict, list, tuple)):
|
|
61
|
+
key = ", ".join(_safe_decode(x) for x in key)
|
|
62
|
+
elif isinstance(key, bytes):
|
|
63
|
+
key = _safe_decode(key)
|
|
64
|
+
elif key is None:
|
|
65
|
+
key = ""
|
|
66
|
+
else:
|
|
67
|
+
key = str(key)
|
|
68
|
+
|
|
69
|
+
return key
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _get_safe_key(method_name, args, kwargs):
|
|
73
|
+
# type: (str, Optional[tuple[Any, ...]], Optional[dict[str, Any]]) -> Optional[tuple[str, ...]]
|
|
74
|
+
"""
|
|
75
|
+
Gets the key (or keys) from the given method_name.
|
|
76
|
+
The method_name could be a redis command or a django caching command
|
|
77
|
+
"""
|
|
78
|
+
key = None
|
|
79
|
+
|
|
80
|
+
if args is not None and method_name.lower() in _MULTI_KEY_COMMANDS:
|
|
81
|
+
# for example redis "mget"
|
|
82
|
+
key = tuple(args)
|
|
83
|
+
|
|
84
|
+
elif args is not None and len(args) >= 1:
|
|
85
|
+
# for example django "set_many/get_many" or redis "get"
|
|
86
|
+
if isinstance(args[0], (dict, list, tuple)):
|
|
87
|
+
key = tuple(args[0])
|
|
88
|
+
else:
|
|
89
|
+
key = (args[0],)
|
|
90
|
+
|
|
91
|
+
elif kwargs is not None and "key" in kwargs:
|
|
92
|
+
# this is a legacy case for older versions of Django
|
|
93
|
+
if isinstance(kwargs["key"], (list, tuple)):
|
|
94
|
+
if len(kwargs["key"]) > 0:
|
|
95
|
+
key = tuple(kwargs["key"])
|
|
96
|
+
else:
|
|
97
|
+
if kwargs["key"] is not None:
|
|
98
|
+
key = (kwargs["key"],)
|
|
99
|
+
|
|
100
|
+
return key
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _parse_rediscluster_command(command):
|
|
104
|
+
# type: (Any) -> Sequence[Any]
|
|
105
|
+
return command.args
|
|
106
|
+
|
|
107
|
+
|
|
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)
|
|
118
|
+
|
|
119
|
+
commands = []
|
|
120
|
+
for i, arg in enumerate(commands_seq):
|
|
121
|
+
if i >= _MAX_NUM_COMMANDS:
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
command = get_command_args_fn(arg)
|
|
125
|
+
commands.append(_get_safe_command(command[0], command[1:]))
|
|
126
|
+
|
|
127
|
+
span.set_data(
|
|
128
|
+
"redis.commands",
|
|
129
|
+
{
|
|
130
|
+
"count": len(commands_seq),
|
|
131
|
+
"first_ten": commands,
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
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)
|
|
139
|
+
if name:
|
|
140
|
+
span.set_tag("redis.command", name)
|
|
141
|
+
span.set_tag(SPANDATA.DB_OPERATION, name)
|
|
142
|
+
|
|
143
|
+
if name and args:
|
|
144
|
+
name_low = name.lower()
|
|
145
|
+
if (name_low in _SINGLE_KEY_COMMANDS) or (
|
|
146
|
+
name_low in _MULTI_KEY_COMMANDS and len(args) == 1
|
|
147
|
+
):
|
|
148
|
+
span.set_tag("redis.key", args[0])
|