sentry-sdk 2.2.0__tar.gz → 2.3.0__tar.gz
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-2.2.0/sentry_sdk.egg-info → sentry_sdk-2.3.0}/PKG-INFO +2 -1
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/consts.py +22 -2
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/celery/__init__.py +54 -5
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/clickhouse_driver.py +8 -7
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/cohere.py +8 -2
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/django/__init__.py +13 -1
- sentry_sdk-2.3.0/sentry_sdk/integrations/django/caching.py +176 -0
- sentry_sdk-2.3.0/sentry_sdk/integrations/redis/__init__.py +37 -0
- sentry_sdk-2.2.0/sentry_sdk/integrations/redis/asyncio.py → sentry_sdk-2.3.0/sentry_sdk/integrations/redis/_async_common.py +49 -14
- sentry_sdk-2.3.0/sentry_sdk/integrations/redis/_sync_common.py +108 -0
- sentry_sdk-2.3.0/sentry_sdk/integrations/redis/consts.py +17 -0
- sentry_sdk-2.3.0/sentry_sdk/integrations/redis/modules/caches.py +114 -0
- sentry_sdk-2.3.0/sentry_sdk/integrations/redis/modules/queries.py +68 -0
- sentry_sdk-2.3.0/sentry_sdk/integrations/redis/rb.py +32 -0
- sentry_sdk-2.3.0/sentry_sdk/integrations/redis/redis.py +69 -0
- sentry_sdk-2.3.0/sentry_sdk/integrations/redis/redis_cluster.py +98 -0
- sentry_sdk-2.3.0/sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
- sentry_sdk-2.3.0/sentry_sdk/integrations/redis/utils.py +116 -0
- sentry_sdk-2.3.0/sentry_sdk/py.typed +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0/sentry_sdk.egg-info}/PKG-INFO +2 -1
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk.egg-info/SOURCES.txt +11 -1
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk.egg-info/requires.txt +1 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/setup.py +2 -2
- sentry_sdk-2.2.0/sentry_sdk/integrations/django/caching.py +0 -115
- sentry_sdk-2.2.0/sentry_sdk/integrations/redis/__init__.py +0 -377
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/LICENSE +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/MANIFEST.in +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/README.md +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/pyproject.toml +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/_compat.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/_lru_cache.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/_queue.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/_types.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/_werkzeug.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/ai/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/ai/monitoring.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/ai/utils.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/api.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/attachments.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/client.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/crons/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/crons/api.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/crons/consts.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/crons/decorator.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/db/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/db/explain_plan/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/db/explain_plan/django.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/db/explain_plan/sqlalchemy.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/debug.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/envelope.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/hub.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/_asgi_common.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/_wsgi_common.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/aiohttp.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/anthropic.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/argv.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/ariadne.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/arq.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/asgi.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/asyncio.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/asyncpg.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/atexit.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/aws_lambda.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/beam.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/boto3.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/bottle.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/celery/beat.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/celery/utils.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/chalice.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/cloud_resource_context.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/dedupe.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/django/asgi.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/django/middleware.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/django/signals_handlers.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/django/templates.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/django/transactions.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/django/views.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/excepthook.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/executing.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/falcon.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/fastapi.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/flask.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/gcp.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/gnu_backtrace.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/gql.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/graphene.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/grpc/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/grpc/aio/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/grpc/aio/client.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/grpc/aio/server.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/grpc/client.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/grpc/server.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/httpx.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/huey.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/huggingface_hub.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/langchain.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/logging.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/loguru.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/modules.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/openai.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/opentelemetry/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/opentelemetry/consts.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/opentelemetry/integration.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/opentelemetry/propagator.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/opentelemetry/span_processor.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/pure_eval.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/pymongo.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/pyramid.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/quart.py +0 -0
- /sentry_sdk-2.2.0/sentry_sdk/py.typed → /sentry_sdk-2.3.0/sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/rq.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/sanic.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/serverless.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/socket.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/spark/__init__.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/spark/spark_driver.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/spark/spark_worker.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/sqlalchemy.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/starlette.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/starlite.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/stdlib.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/strawberry.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/threading.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/tornado.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/trytond.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/integrations/wsgi.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/metrics.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/monitor.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/profiler.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/scope.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/scrubber.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/serializer.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/session.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/sessions.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/spotlight.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/tracing.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/tracing_utils.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/transport.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/types.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/utils.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk/worker.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk.egg-info/dependency_links.txt +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk.egg-info/not-zip-safe +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/sentry_sdk.egg-info/top_level.txt +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/setup.cfg +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_api.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_basics.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_client.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_conftest.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_crons.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_envelope.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_exceptiongroup.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_lru_cache.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_metrics.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_monitor.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_new_scopes_compat.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_new_scopes_compat_event.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_profiler.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_propagationcontext.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_scope.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_scrubber.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_serializer.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_sessions.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_spotlight.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_transport.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_types.py +0 -0
- {sentry_sdk-2.2.0 → sentry_sdk-2.3.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sentry-sdk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Python client for Sentry (https://sentry.io)
|
|
5
5
|
Home-page: https://github.com/getsentry/sentry-python
|
|
6
6
|
Author: Sentry Team and Contributors
|
|
@@ -60,6 +60,7 @@ Requires-Dist: blinker>=1.1; extra == "flask"
|
|
|
60
60
|
Requires-Dist: markupsafe; extra == "flask"
|
|
61
61
|
Provides-Extra: grpcio
|
|
62
62
|
Requires-Dist: grpcio>=1.21.1; extra == "grpcio"
|
|
63
|
+
Requires-Dist: protobuf>=3.8.0; extra == "grpcio"
|
|
63
64
|
Provides-Extra: httpx
|
|
64
65
|
Requires-Dist: httpx>=0.16.0; extra == "httpx"
|
|
65
66
|
Provides-Extra: huey
|
|
@@ -240,6 +240,24 @@ class SPANDATA:
|
|
|
240
240
|
Example: 58
|
|
241
241
|
"""
|
|
242
242
|
|
|
243
|
+
CACHE_KEY = "cache.key"
|
|
244
|
+
"""
|
|
245
|
+
The key of the requested data.
|
|
246
|
+
Example: template.cache.some_item.867da7e2af8e6b2f3aa7213a4080edb3
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
NETWORK_PEER_ADDRESS = "network.peer.address"
|
|
250
|
+
"""
|
|
251
|
+
Peer address of the network connection - IP address or Unix domain socket name.
|
|
252
|
+
Example: 10.1.2.80, /tmp/my.sock, localhost
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
NETWORK_PEER_PORT = "network.peer.port"
|
|
256
|
+
"""
|
|
257
|
+
Peer port number of the network connection.
|
|
258
|
+
Example: 6379
|
|
259
|
+
"""
|
|
260
|
+
|
|
243
261
|
HTTP_QUERY = "http.query"
|
|
244
262
|
"""
|
|
245
263
|
The Query string present in the URL.
|
|
@@ -349,7 +367,8 @@ class SPANDATA:
|
|
|
349
367
|
|
|
350
368
|
class OP:
|
|
351
369
|
ANTHROPIC_MESSAGES_CREATE = "ai.messages.create.anthropic"
|
|
352
|
-
|
|
370
|
+
CACHE_GET = "cache.get"
|
|
371
|
+
CACHE_PUT = "cache.put"
|
|
353
372
|
COHERE_CHAT_COMPLETIONS_CREATE = "ai.chat_completions.create.cohere"
|
|
354
373
|
COHERE_EMBEDDINGS_CREATE = "ai.embeddings.create.cohere"
|
|
355
374
|
DB = "db"
|
|
@@ -388,6 +407,7 @@ class OP:
|
|
|
388
407
|
LANGCHAIN_AGENT = "ai.agent.langchain"
|
|
389
408
|
LANGCHAIN_CHAT_COMPLETIONS_CREATE = "ai.chat_completions.create.langchain"
|
|
390
409
|
QUEUE_PROCESS = "queue.process"
|
|
410
|
+
QUEUE_PUBLISH = "queue.publish"
|
|
391
411
|
QUEUE_SUBMIT_ARQ = "queue.submit.arq"
|
|
392
412
|
QUEUE_TASK_ARQ = "queue.task.arq"
|
|
393
413
|
QUEUE_SUBMIT_CELERY = "queue.submit.celery"
|
|
@@ -488,4 +508,4 @@ DEFAULT_OPTIONS = _get_default_options()
|
|
|
488
508
|
del _get_default_options
|
|
489
509
|
|
|
490
510
|
|
|
491
|
-
VERSION = "2.
|
|
511
|
+
VERSION = "2.3.0"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
from collections.abc import Mapping
|
|
2
3
|
from functools import wraps
|
|
3
4
|
|
|
4
5
|
import sentry_sdk
|
|
@@ -47,6 +48,7 @@ try:
|
|
|
47
48
|
Retry,
|
|
48
49
|
SoftTimeLimitExceeded,
|
|
49
50
|
)
|
|
51
|
+
from kombu import Producer # type: ignore
|
|
50
52
|
except ImportError:
|
|
51
53
|
raise DidNotEnable("Celery not installed")
|
|
52
54
|
|
|
@@ -82,6 +84,7 @@ class CeleryIntegration(Integration):
|
|
|
82
84
|
_patch_build_tracer()
|
|
83
85
|
_patch_task_apply_async()
|
|
84
86
|
_patch_worker_exit()
|
|
87
|
+
_patch_producer_publish()
|
|
85
88
|
|
|
86
89
|
# This logger logs every status of every task that ran on the worker.
|
|
87
90
|
# Meaning that every task's breadcrumbs are full of stuff like "Task
|
|
@@ -330,11 +333,12 @@ def _set_messaging_destination_name(task, span):
|
|
|
330
333
|
"""Set "messaging.destination.name" tag for span"""
|
|
331
334
|
with capture_internal_exceptions():
|
|
332
335
|
delivery_info = task.request.delivery_info
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
336
|
+
if delivery_info:
|
|
337
|
+
routing_key = delivery_info.get("routing_key")
|
|
338
|
+
if delivery_info.get("exchange") == "" and routing_key is not None:
|
|
339
|
+
# Empty exchange indicates the default exchange, meaning the tasks
|
|
340
|
+
# are sent to the queue with the same name as the routing key.
|
|
341
|
+
span.set_data(SPANDATA.MESSAGING_DESTINATION_NAME, routing_key)
|
|
338
342
|
|
|
339
343
|
|
|
340
344
|
def _wrap_task_call(task, f):
|
|
@@ -433,3 +437,48 @@ def _patch_worker_exit():
|
|
|
433
437
|
sentry_sdk.flush()
|
|
434
438
|
|
|
435
439
|
Worker.workloop = sentry_workloop
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _patch_producer_publish():
|
|
443
|
+
# type: () -> None
|
|
444
|
+
original_publish = Producer.publish
|
|
445
|
+
|
|
446
|
+
@ensure_integration_enabled(CeleryIntegration, original_publish)
|
|
447
|
+
def sentry_publish(self, *args, **kwargs):
|
|
448
|
+
# type: (Producer, *Any, **Any) -> Any
|
|
449
|
+
kwargs_headers = kwargs.get("headers", {})
|
|
450
|
+
if not isinstance(kwargs_headers, Mapping):
|
|
451
|
+
# Ensure kwargs_headers is a Mapping, so we can safely call get().
|
|
452
|
+
# We don't expect this to happen, but it's better to be safe. Even
|
|
453
|
+
# if it does happen, only our instrumentation breaks. This line
|
|
454
|
+
# does not overwrite kwargs["headers"], so the original publish
|
|
455
|
+
# method will still work.
|
|
456
|
+
kwargs_headers = {}
|
|
457
|
+
|
|
458
|
+
task_name = kwargs_headers.get("task")
|
|
459
|
+
task_id = kwargs_headers.get("id")
|
|
460
|
+
retries = kwargs_headers.get("retries")
|
|
461
|
+
|
|
462
|
+
routing_key = kwargs.get("routing_key")
|
|
463
|
+
exchange = kwargs.get("exchange")
|
|
464
|
+
|
|
465
|
+
with sentry_sdk.start_span(op=OP.QUEUE_PUBLISH, description=task_name) as span:
|
|
466
|
+
if task_id is not None:
|
|
467
|
+
span.set_data(SPANDATA.MESSAGING_MESSAGE_ID, task_id)
|
|
468
|
+
|
|
469
|
+
if exchange == "" and routing_key is not None:
|
|
470
|
+
# Empty exchange indicates the default exchange, meaning messages are
|
|
471
|
+
# routed to the queue with the same name as the routing key.
|
|
472
|
+
span.set_data(SPANDATA.MESSAGING_DESTINATION_NAME, routing_key)
|
|
473
|
+
|
|
474
|
+
if retries is not None:
|
|
475
|
+
span.set_data(SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, retries)
|
|
476
|
+
|
|
477
|
+
with capture_internal_exceptions():
|
|
478
|
+
span.set_data(
|
|
479
|
+
SPANDATA.MESSAGING_SYSTEM, self.connection.transport.driver_type
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
return original_publish(self, *args, **kwargs)
|
|
483
|
+
|
|
484
|
+
Producer.publish = sentry_publish
|
|
@@ -107,7 +107,7 @@ def _wrap_end(f: Callable[P, T]) -> Callable[P, T]:
|
|
|
107
107
|
def _inner_end(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
108
108
|
res = f(*args, **kwargs)
|
|
109
109
|
instance = args[0]
|
|
110
|
-
span = instance.connection
|
|
110
|
+
span = getattr(instance.connection, "_sentry_span", None) # type: ignore[attr-defined]
|
|
111
111
|
|
|
112
112
|
if span is not None:
|
|
113
113
|
if res is not None and should_send_default_pii():
|
|
@@ -129,14 +129,15 @@ def _wrap_send_data(f: Callable[P, T]) -> Callable[P, T]:
|
|
|
129
129
|
def _inner_send_data(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
130
130
|
instance = args[0] # type: clickhouse_driver.client.Client
|
|
131
131
|
data = args[2]
|
|
132
|
-
span = instance.connection
|
|
132
|
+
span = getattr(instance.connection, "_sentry_span", None)
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
if span is not None:
|
|
135
|
+
_set_db_data(span, instance.connection)
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
if should_send_default_pii():
|
|
138
|
+
db_params = span._data.get("db.params", [])
|
|
139
|
+
db_params.extend(data)
|
|
140
|
+
span.set_data("db.params", db_params)
|
|
140
141
|
|
|
141
142
|
return f(*args, **kwargs)
|
|
142
143
|
|
|
@@ -22,7 +22,11 @@ from sentry_sdk.utils import (
|
|
|
22
22
|
try:
|
|
23
23
|
from cohere.client import Client
|
|
24
24
|
from cohere.base_client import BaseCohere
|
|
25
|
-
from cohere import
|
|
25
|
+
from cohere import (
|
|
26
|
+
ChatStreamEndEvent,
|
|
27
|
+
NonStreamedChatResponse,
|
|
28
|
+
StreamedChatResponse_StreamEnd,
|
|
29
|
+
)
|
|
26
30
|
|
|
27
31
|
if TYPE_CHECKING:
|
|
28
32
|
from cohere import StreamedChatResponse
|
|
@@ -181,7 +185,9 @@ def _wrap_chat(f, streaming):
|
|
|
181
185
|
|
|
182
186
|
with capture_internal_exceptions():
|
|
183
187
|
for x in old_iterator:
|
|
184
|
-
if isinstance(x, ChatStreamEndEvent)
|
|
188
|
+
if isinstance(x, ChatStreamEndEvent) or isinstance(
|
|
189
|
+
x, StreamedChatResponse_StreamEnd
|
|
190
|
+
):
|
|
185
191
|
collect_chat_response_fields(
|
|
186
192
|
span,
|
|
187
193
|
x.response,
|
|
@@ -104,6 +104,16 @@ TRANSACTION_STYLE_VALUES = ("function_name", "url")
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
class DjangoIntegration(Integration):
|
|
107
|
+
"""
|
|
108
|
+
Auto instrument a Django application.
|
|
109
|
+
|
|
110
|
+
:param transaction_style: How to derive transaction names. Either `"function_name"` or `"url"`. Defaults to `"url"`.
|
|
111
|
+
:param middleware_spans: Whether to create spans for middleware. Defaults to `True`.
|
|
112
|
+
:param signals_spans: Whether to create spans for signals. Defaults to `True`.
|
|
113
|
+
:param signals_denylist: A list of signals to ignore when creating spans.
|
|
114
|
+
:param cache_spans: Whether to create spans for cache operations. Defaults to `False`.
|
|
115
|
+
"""
|
|
116
|
+
|
|
107
117
|
identifier = "django"
|
|
108
118
|
|
|
109
119
|
transaction_style = ""
|
|
@@ -128,10 +138,12 @@ class DjangoIntegration(Integration):
|
|
|
128
138
|
)
|
|
129
139
|
self.transaction_style = transaction_style
|
|
130
140
|
self.middleware_spans = middleware_spans
|
|
141
|
+
|
|
131
142
|
self.signals_spans = signals_spans
|
|
132
|
-
self.cache_spans = cache_spans
|
|
133
143
|
self.signals_denylist = signals_denylist or []
|
|
134
144
|
|
|
145
|
+
self.cache_spans = cache_spans
|
|
146
|
+
|
|
135
147
|
@staticmethod
|
|
136
148
|
def setup_once():
|
|
137
149
|
# type: () -> None
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
from sentry_sdk.integrations.redis.utils import _get_safe_key
|
|
4
|
+
from urllib3.util import parse_url as urlparse
|
|
5
|
+
|
|
6
|
+
from django import VERSION as DJANGO_VERSION
|
|
7
|
+
from django.core.cache import CacheHandler
|
|
8
|
+
|
|
9
|
+
import sentry_sdk
|
|
10
|
+
from sentry_sdk.consts import OP, SPANDATA
|
|
11
|
+
from sentry_sdk.utils import (
|
|
12
|
+
capture_internal_exceptions,
|
|
13
|
+
ensure_integration_enabled,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from typing import Any
|
|
19
|
+
from typing import Callable
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
METHODS_TO_INSTRUMENT = [
|
|
24
|
+
"set",
|
|
25
|
+
"set_many",
|
|
26
|
+
"get",
|
|
27
|
+
"get_many",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _get_span_description(method_name, args, kwargs):
|
|
32
|
+
# type: (str, tuple[Any], dict[str, Any]) -> str
|
|
33
|
+
return _get_safe_key(method_name, args, kwargs)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _patch_cache_method(cache, method_name, address, port):
|
|
37
|
+
# type: (CacheHandler, str, Optional[str], Optional[int]) -> None
|
|
38
|
+
from sentry_sdk.integrations.django import DjangoIntegration
|
|
39
|
+
|
|
40
|
+
original_method = getattr(cache, method_name)
|
|
41
|
+
|
|
42
|
+
@ensure_integration_enabled(DjangoIntegration, original_method)
|
|
43
|
+
def _instrument_call(
|
|
44
|
+
cache, method_name, original_method, args, kwargs, address, port
|
|
45
|
+
):
|
|
46
|
+
# type: (CacheHandler, str, Callable[..., Any], tuple[Any, ...], dict[str, Any], Optional[str], Optional[int]) -> Any
|
|
47
|
+
is_set_operation = method_name.startswith("set")
|
|
48
|
+
is_get_operation = not is_set_operation
|
|
49
|
+
|
|
50
|
+
op = OP.CACHE_PUT if is_set_operation else OP.CACHE_GET
|
|
51
|
+
description = _get_span_description(method_name, args, kwargs)
|
|
52
|
+
|
|
53
|
+
with sentry_sdk.start_span(op=op, description=description) as span:
|
|
54
|
+
value = original_method(*args, **kwargs)
|
|
55
|
+
|
|
56
|
+
with capture_internal_exceptions():
|
|
57
|
+
if address is not None:
|
|
58
|
+
span.set_data(SPANDATA.NETWORK_PEER_ADDRESS, address)
|
|
59
|
+
|
|
60
|
+
if port is not None:
|
|
61
|
+
span.set_data(SPANDATA.NETWORK_PEER_PORT, port)
|
|
62
|
+
|
|
63
|
+
key = _get_safe_key(method_name, args, kwargs)
|
|
64
|
+
if key != "":
|
|
65
|
+
span.set_data(SPANDATA.CACHE_KEY, key)
|
|
66
|
+
|
|
67
|
+
item_size = None
|
|
68
|
+
if is_get_operation:
|
|
69
|
+
if value:
|
|
70
|
+
item_size = len(str(value))
|
|
71
|
+
span.set_data(SPANDATA.CACHE_HIT, True)
|
|
72
|
+
else:
|
|
73
|
+
span.set_data(SPANDATA.CACHE_HIT, False)
|
|
74
|
+
else:
|
|
75
|
+
try:
|
|
76
|
+
# 'set' command
|
|
77
|
+
item_size = len(str(args[1]))
|
|
78
|
+
except IndexError:
|
|
79
|
+
# 'set_many' command
|
|
80
|
+
item_size = len(str(args[0]))
|
|
81
|
+
|
|
82
|
+
if item_size is not None:
|
|
83
|
+
span.set_data(SPANDATA.CACHE_ITEM_SIZE, item_size)
|
|
84
|
+
|
|
85
|
+
return value
|
|
86
|
+
|
|
87
|
+
@functools.wraps(original_method)
|
|
88
|
+
def sentry_method(*args, **kwargs):
|
|
89
|
+
# type: (*Any, **Any) -> Any
|
|
90
|
+
return _instrument_call(
|
|
91
|
+
cache, method_name, original_method, args, kwargs, address, port
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
setattr(cache, method_name, sentry_method)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _patch_cache(cache, address=None, port=None):
|
|
98
|
+
# type: (CacheHandler, Optional[str], Optional[int]) -> None
|
|
99
|
+
if not hasattr(cache, "_sentry_patched"):
|
|
100
|
+
for method_name in METHODS_TO_INSTRUMENT:
|
|
101
|
+
_patch_cache_method(cache, method_name, address, port)
|
|
102
|
+
cache._sentry_patched = True
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _get_address_port(settings):
|
|
106
|
+
# type: (dict[str, Any]) -> tuple[Optional[str], Optional[int]]
|
|
107
|
+
location = settings.get("LOCATION")
|
|
108
|
+
|
|
109
|
+
# TODO: location can also be an array of locations
|
|
110
|
+
# see: https://docs.djangoproject.com/en/5.0/topics/cache/#redis
|
|
111
|
+
# GitHub issue: https://github.com/getsentry/sentry-python/issues/3062
|
|
112
|
+
if not isinstance(location, str):
|
|
113
|
+
return None, None
|
|
114
|
+
|
|
115
|
+
if "://" in location:
|
|
116
|
+
parsed_url = urlparse(location)
|
|
117
|
+
# remove the username and password from URL to not leak sensitive data.
|
|
118
|
+
address = "{}://{}{}".format(
|
|
119
|
+
parsed_url.scheme or "",
|
|
120
|
+
parsed_url.hostname or "",
|
|
121
|
+
parsed_url.path or "",
|
|
122
|
+
)
|
|
123
|
+
port = parsed_url.port
|
|
124
|
+
else:
|
|
125
|
+
address = location
|
|
126
|
+
port = None
|
|
127
|
+
|
|
128
|
+
return address, int(port) if port is not None else None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def patch_caching():
|
|
132
|
+
# type: () -> None
|
|
133
|
+
from sentry_sdk.integrations.django import DjangoIntegration
|
|
134
|
+
|
|
135
|
+
if not hasattr(CacheHandler, "_sentry_patched"):
|
|
136
|
+
if DJANGO_VERSION < (3, 2):
|
|
137
|
+
original_get_item = CacheHandler.__getitem__
|
|
138
|
+
|
|
139
|
+
@functools.wraps(original_get_item)
|
|
140
|
+
def sentry_get_item(self, alias):
|
|
141
|
+
# type: (CacheHandler, str) -> Any
|
|
142
|
+
cache = original_get_item(self, alias)
|
|
143
|
+
|
|
144
|
+
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
|
|
145
|
+
if integration is not None and integration.cache_spans:
|
|
146
|
+
from django.conf import settings
|
|
147
|
+
|
|
148
|
+
address, port = _get_address_port(
|
|
149
|
+
settings.CACHES[alias or "default"]
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
_patch_cache(cache, address, port)
|
|
153
|
+
|
|
154
|
+
return cache
|
|
155
|
+
|
|
156
|
+
CacheHandler.__getitem__ = sentry_get_item
|
|
157
|
+
CacheHandler._sentry_patched = True
|
|
158
|
+
|
|
159
|
+
else:
|
|
160
|
+
original_create_connection = CacheHandler.create_connection
|
|
161
|
+
|
|
162
|
+
@functools.wraps(original_create_connection)
|
|
163
|
+
def sentry_create_connection(self, alias):
|
|
164
|
+
# type: (CacheHandler, str) -> Any
|
|
165
|
+
cache = original_create_connection(self, alias)
|
|
166
|
+
|
|
167
|
+
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
|
|
168
|
+
if integration is not None and integration.cache_spans:
|
|
169
|
+
address, port = _get_address_port(self.settings[alias or "default"])
|
|
170
|
+
|
|
171
|
+
_patch_cache(cache, address, port)
|
|
172
|
+
|
|
173
|
+
return cache
|
|
174
|
+
|
|
175
|
+
CacheHandler.create_connection = sentry_create_connection
|
|
176
|
+
CacheHandler._sentry_patched = True
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from sentry_sdk._types import TYPE_CHECKING
|
|
2
|
+
from sentry_sdk.integrations import Integration, DidNotEnable
|
|
3
|
+
from sentry_sdk.integrations.redis.consts import _DEFAULT_MAX_DATA_SIZE
|
|
4
|
+
from sentry_sdk.integrations.redis.rb import _patch_rb
|
|
5
|
+
from sentry_sdk.integrations.redis.redis import _patch_redis
|
|
6
|
+
from sentry_sdk.integrations.redis.redis_cluster import _patch_redis_cluster
|
|
7
|
+
from sentry_sdk.integrations.redis.redis_py_cluster_legacy import _patch_rediscluster
|
|
8
|
+
from sentry_sdk.utils import logger
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RedisIntegration(Integration):
|
|
15
|
+
identifier = "redis"
|
|
16
|
+
|
|
17
|
+
def __init__(self, max_data_size=_DEFAULT_MAX_DATA_SIZE, cache_prefixes=None):
|
|
18
|
+
# type: (int, Optional[list[str]]) -> None
|
|
19
|
+
self.max_data_size = max_data_size
|
|
20
|
+
self.cache_prefixes = cache_prefixes if cache_prefixes is not None else []
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def setup_once():
|
|
24
|
+
# type: () -> None
|
|
25
|
+
try:
|
|
26
|
+
from redis import StrictRedis, client
|
|
27
|
+
except ImportError:
|
|
28
|
+
raise DidNotEnable("Redis client not installed")
|
|
29
|
+
|
|
30
|
+
_patch_redis(StrictRedis, client)
|
|
31
|
+
_patch_redis_cluster()
|
|
32
|
+
_patch_rb()
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
_patch_rediscluster()
|
|
36
|
+
except Exception:
|
|
37
|
+
logger.exception("Error occurred while patching `rediscluster` library")
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
from sentry_sdk._types import TYPE_CHECKING
|
|
2
2
|
from sentry_sdk.consts import OP
|
|
3
|
-
from sentry_sdk.integrations.redis import (
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
from sentry_sdk.integrations.redis.modules.caches import (
|
|
4
|
+
_compile_cache_span_properties,
|
|
5
|
+
_set_cache_data,
|
|
6
|
+
)
|
|
7
|
+
from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
|
|
8
|
+
from sentry_sdk.integrations.redis.utils import (
|
|
6
9
|
_set_client_data,
|
|
7
10
|
_set_pipeline_data,
|
|
8
11
|
)
|
|
9
|
-
from sentry_sdk._types import TYPE_CHECKING
|
|
10
12
|
from sentry_sdk.tracing import Span
|
|
11
|
-
from sentry_sdk.utils import
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
from sentry_sdk.utils import capture_internal_exceptions
|
|
14
|
+
import sentry_sdk
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
if TYPE_CHECKING:
|
|
16
18
|
from collections.abc import Callable
|
|
@@ -25,6 +27,8 @@ def patch_redis_async_pipeline(
|
|
|
25
27
|
# type: (Union[type[Pipeline[Any]], type[ClusterPipeline[Any]]], bool, Any, Callable[[Span, Any], None]) -> None
|
|
26
28
|
old_execute = pipeline_cls.execute
|
|
27
29
|
|
|
30
|
+
from sentry_sdk.integrations.redis import RedisIntegration
|
|
31
|
+
|
|
28
32
|
async def _sentry_execute(self, *args, **kwargs):
|
|
29
33
|
# type: (Any, *Any, **Any) -> Any
|
|
30
34
|
if sentry_sdk.get_client().get_integration(RedisIntegration) is None:
|
|
@@ -52,17 +56,48 @@ def patch_redis_async_client(cls, is_cluster, set_db_data_fn):
|
|
|
52
56
|
# type: (Union[type[StrictRedis[Any]], type[RedisCluster[Any]]], bool, Callable[[Span, Any], None]) -> None
|
|
53
57
|
old_execute_command = cls.execute_command
|
|
54
58
|
|
|
59
|
+
from sentry_sdk.integrations.redis import RedisIntegration
|
|
60
|
+
|
|
55
61
|
async def _sentry_execute_command(self, name, *args, **kwargs):
|
|
56
62
|
# type: (Any, str, *Any, **Any) -> Any
|
|
57
|
-
|
|
63
|
+
integration = sentry_sdk.get_client().get_integration(RedisIntegration)
|
|
64
|
+
if integration is None:
|
|
58
65
|
return await old_execute_command(self, name, *args, **kwargs)
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
cache_properties = _compile_cache_span_properties(
|
|
68
|
+
name,
|
|
69
|
+
args,
|
|
70
|
+
kwargs,
|
|
71
|
+
integration,
|
|
72
|
+
)
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
cache_span = None
|
|
75
|
+
if cache_properties["is_cache_key"] and cache_properties["op"] is not None:
|
|
76
|
+
cache_span = sentry_sdk.start_span(
|
|
77
|
+
op=cache_properties["op"],
|
|
78
|
+
description=cache_properties["description"],
|
|
79
|
+
)
|
|
80
|
+
cache_span.__enter__()
|
|
65
81
|
|
|
66
|
-
|
|
82
|
+
db_properties = _compile_db_span_properties(integration, name, args)
|
|
83
|
+
|
|
84
|
+
db_span = sentry_sdk.start_span(
|
|
85
|
+
op=db_properties["op"],
|
|
86
|
+
description=db_properties["description"],
|
|
87
|
+
)
|
|
88
|
+
db_span.__enter__()
|
|
89
|
+
|
|
90
|
+
set_db_data_fn(db_span, self)
|
|
91
|
+
_set_client_data(db_span, is_cluster, name, *args)
|
|
92
|
+
|
|
93
|
+
value = await old_execute_command(self, name, *args, **kwargs)
|
|
94
|
+
|
|
95
|
+
db_span.__exit__(None, None, None)
|
|
96
|
+
|
|
97
|
+
if cache_span:
|
|
98
|
+
_set_cache_data(cache_span, self, cache_properties, value)
|
|
99
|
+
cache_span.__exit__(None, None, None)
|
|
100
|
+
|
|
101
|
+
return value
|
|
67
102
|
|
|
68
103
|
cls.execute_command = _sentry_execute_command # type: ignore
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from sentry_sdk._types import TYPE_CHECKING
|
|
2
|
+
from sentry_sdk.consts import OP
|
|
3
|
+
from sentry_sdk.integrations.redis.modules.caches import (
|
|
4
|
+
_compile_cache_span_properties,
|
|
5
|
+
_set_cache_data,
|
|
6
|
+
)
|
|
7
|
+
from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
|
|
8
|
+
from sentry_sdk.integrations.redis.utils import (
|
|
9
|
+
_set_client_data,
|
|
10
|
+
_set_pipeline_data,
|
|
11
|
+
)
|
|
12
|
+
from sentry_sdk.tracing import Span
|
|
13
|
+
from sentry_sdk.utils import capture_internal_exceptions
|
|
14
|
+
import sentry_sdk
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def patch_redis_pipeline(
|
|
23
|
+
pipeline_cls,
|
|
24
|
+
is_cluster,
|
|
25
|
+
get_command_args_fn,
|
|
26
|
+
set_db_data_fn,
|
|
27
|
+
):
|
|
28
|
+
# type: (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
|
+
def sentry_patched_execute(self, *args, **kwargs):
|
|
34
|
+
# type: (Any, *Any, **Any) -> Any
|
|
35
|
+
if sentry_sdk.get_client().get_integration(RedisIntegration) is None:
|
|
36
|
+
return old_execute(self, *args, **kwargs)
|
|
37
|
+
|
|
38
|
+
with sentry_sdk.start_span(
|
|
39
|
+
op=OP.DB_REDIS, description="redis.pipeline.execute"
|
|
40
|
+
) as span:
|
|
41
|
+
with capture_internal_exceptions():
|
|
42
|
+
set_db_data_fn(span, self)
|
|
43
|
+
_set_pipeline_data(
|
|
44
|
+
span,
|
|
45
|
+
is_cluster,
|
|
46
|
+
get_command_args_fn,
|
|
47
|
+
False if is_cluster else self.transaction,
|
|
48
|
+
self.command_stack,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return old_execute(self, *args, **kwargs)
|
|
52
|
+
|
|
53
|
+
pipeline_cls.execute = sentry_patched_execute
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def patch_redis_client(cls, is_cluster, set_db_data_fn):
|
|
57
|
+
# type: (Any, bool, Callable[[Span, Any], None]) -> None
|
|
58
|
+
"""
|
|
59
|
+
This function can be used to instrument custom redis client classes or
|
|
60
|
+
subclasses.
|
|
61
|
+
"""
|
|
62
|
+
old_execute_command = cls.execute_command
|
|
63
|
+
|
|
64
|
+
from sentry_sdk.integrations.redis import RedisIntegration
|
|
65
|
+
|
|
66
|
+
def sentry_patched_execute_command(self, name, *args, **kwargs):
|
|
67
|
+
# type: (Any, str, *Any, **Any) -> Any
|
|
68
|
+
integration = sentry_sdk.get_client().get_integration(RedisIntegration)
|
|
69
|
+
if integration is None:
|
|
70
|
+
return old_execute_command(self, name, *args, **kwargs)
|
|
71
|
+
|
|
72
|
+
cache_properties = _compile_cache_span_properties(
|
|
73
|
+
name,
|
|
74
|
+
args,
|
|
75
|
+
kwargs,
|
|
76
|
+
integration,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
cache_span = None
|
|
80
|
+
if cache_properties["is_cache_key"] and cache_properties["op"] is not None:
|
|
81
|
+
cache_span = sentry_sdk.start_span(
|
|
82
|
+
op=cache_properties["op"],
|
|
83
|
+
description=cache_properties["description"],
|
|
84
|
+
)
|
|
85
|
+
cache_span.__enter__()
|
|
86
|
+
|
|
87
|
+
db_properties = _compile_db_span_properties(integration, name, args)
|
|
88
|
+
|
|
89
|
+
db_span = sentry_sdk.start_span(
|
|
90
|
+
op=db_properties["op"],
|
|
91
|
+
description=db_properties["description"],
|
|
92
|
+
)
|
|
93
|
+
db_span.__enter__()
|
|
94
|
+
|
|
95
|
+
set_db_data_fn(db_span, self)
|
|
96
|
+
_set_client_data(db_span, is_cluster, name, *args)
|
|
97
|
+
|
|
98
|
+
value = old_execute_command(self, name, *args, **kwargs)
|
|
99
|
+
|
|
100
|
+
db_span.__exit__(None, None, None)
|
|
101
|
+
|
|
102
|
+
if cache_span:
|
|
103
|
+
_set_cache_data(cache_span, self, cache_properties, value)
|
|
104
|
+
cache_span.__exit__(None, None, None)
|
|
105
|
+
|
|
106
|
+
return value
|
|
107
|
+
|
|
108
|
+
cls.execute_command = sentry_patched_execute_command
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
_SINGLE_KEY_COMMANDS = frozenset(
|
|
2
|
+
["decr", "decrby", "get", "incr", "incrby", "pttl", "set", "setex", "setnx", "ttl"],
|
|
3
|
+
)
|
|
4
|
+
_MULTI_KEY_COMMANDS = frozenset(
|
|
5
|
+
[
|
|
6
|
+
"del",
|
|
7
|
+
"touch",
|
|
8
|
+
"unlink",
|
|
9
|
+
"mget",
|
|
10
|
+
],
|
|
11
|
+
)
|
|
12
|
+
_COMMANDS_INCLUDING_SENSITIVE_DATA = [
|
|
13
|
+
"auth",
|
|
14
|
+
]
|
|
15
|
+
_MAX_NUM_ARGS = 10 # Trim argument lists to this many values
|
|
16
|
+
_MAX_NUM_COMMANDS = 10 # Trim command lists to this many values
|
|
17
|
+
_DEFAULT_MAX_DATA_SIZE = 1024
|