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,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])
|
sentry_sdk/integrations/rq.py
CHANGED
|
@@ -1,83 +1,80 @@
|
|
|
1
|
-
from __future__ import absolute_import
|
|
2
|
-
|
|
3
1
|
import weakref
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
from sentry_sdk.
|
|
7
|
-
from sentry_sdk.
|
|
8
|
-
from sentry_sdk.
|
|
9
|
-
|
|
3
|
+
import sentry_sdk
|
|
4
|
+
from sentry_sdk.consts import OP
|
|
5
|
+
from sentry_sdk.api import continue_trace
|
|
6
|
+
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
|
|
7
|
+
from sentry_sdk.integrations.logging import ignore_logger
|
|
8
|
+
from sentry_sdk.tracing import TransactionSource
|
|
9
|
+
from sentry_sdk.utils import (
|
|
10
|
+
capture_internal_exceptions,
|
|
11
|
+
ensure_integration_enabled,
|
|
12
|
+
event_from_exception,
|
|
13
|
+
format_timestamp,
|
|
14
|
+
parse_version,
|
|
15
|
+
)
|
|
10
16
|
|
|
11
17
|
try:
|
|
12
|
-
from rq.
|
|
18
|
+
from rq.queue import Queue
|
|
13
19
|
from rq.timeouts import JobTimeoutException
|
|
20
|
+
from rq.version import VERSION as RQ_VERSION
|
|
14
21
|
from rq.worker import Worker
|
|
15
|
-
from rq.
|
|
22
|
+
from rq.job import JobStatus
|
|
16
23
|
except ImportError:
|
|
17
24
|
raise DidNotEnable("RQ not installed")
|
|
18
25
|
|
|
19
|
-
from
|
|
20
|
-
|
|
21
|
-
if MYPY:
|
|
22
|
-
from typing import Any
|
|
23
|
-
from typing import Dict
|
|
24
|
-
from typing import Callable
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from typing import Any, Callable
|
|
27
30
|
|
|
31
|
+
from sentry_sdk._types import Event, EventProcessor
|
|
28
32
|
from sentry_sdk.utils import ExcInfo
|
|
29
|
-
|
|
33
|
+
|
|
34
|
+
from rq.job import Job
|
|
30
35
|
|
|
31
36
|
|
|
32
37
|
class RqIntegration(Integration):
|
|
33
38
|
identifier = "rq"
|
|
39
|
+
origin = f"auto.queue.{identifier}"
|
|
34
40
|
|
|
35
41
|
@staticmethod
|
|
36
42
|
def setup_once():
|
|
37
43
|
# type: () -> None
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
version = tuple(map(int, RQ_VERSION.split(".")[:3]))
|
|
41
|
-
except (ValueError, TypeError):
|
|
42
|
-
raise DidNotEnable("Unparsable RQ version: {}".format(RQ_VERSION))
|
|
43
|
-
|
|
44
|
-
if version < (0, 6):
|
|
45
|
-
raise DidNotEnable("RQ 0.6 or newer is required.")
|
|
44
|
+
version = parse_version(RQ_VERSION)
|
|
45
|
+
_check_minimum_version(RqIntegration, version)
|
|
46
46
|
|
|
47
47
|
old_perform_job = Worker.perform_job
|
|
48
48
|
|
|
49
|
+
@ensure_integration_enabled(RqIntegration, old_perform_job)
|
|
49
50
|
def sentry_patched_perform_job(self, job, *args, **kwargs):
|
|
50
51
|
# type: (Any, Job, *Queue, **Any) -> bool
|
|
51
|
-
|
|
52
|
-
integration = hub.get_integration(RqIntegration)
|
|
53
|
-
|
|
54
|
-
if integration is None:
|
|
55
|
-
return old_perform_job(self, job, *args, **kwargs)
|
|
56
|
-
|
|
57
|
-
client = hub.client
|
|
58
|
-
assert client is not None
|
|
59
|
-
|
|
60
|
-
with hub.push_scope() as scope:
|
|
52
|
+
with sentry_sdk.new_scope() as scope:
|
|
61
53
|
scope.clear_breadcrumbs()
|
|
62
54
|
scope.add_event_processor(_make_event_processor(weakref.ref(job)))
|
|
63
55
|
|
|
64
|
-
transaction =
|
|
56
|
+
transaction = continue_trace(
|
|
65
57
|
job.meta.get("_sentry_trace_headers") or {},
|
|
66
|
-
op=
|
|
58
|
+
op=OP.QUEUE_TASK_RQ,
|
|
67
59
|
name="unknown RQ task",
|
|
60
|
+
source=TransactionSource.TASK,
|
|
61
|
+
origin=RqIntegration.origin,
|
|
68
62
|
)
|
|
69
63
|
|
|
70
64
|
with capture_internal_exceptions():
|
|
71
65
|
transaction.name = job.func_name
|
|
72
66
|
|
|
73
|
-
with
|
|
67
|
+
with sentry_sdk.start_transaction(
|
|
68
|
+
transaction,
|
|
69
|
+
custom_sampling_context={"rq_job": job},
|
|
70
|
+
):
|
|
74
71
|
rv = old_perform_job(self, job, *args, **kwargs)
|
|
75
72
|
|
|
76
73
|
if self.is_horse:
|
|
77
74
|
# We're inside of a forked process and RQ is
|
|
78
75
|
# about to call `os._exit`. Make sure that our
|
|
79
76
|
# events get sent out.
|
|
80
|
-
|
|
77
|
+
sentry_sdk.get_client().flush()
|
|
81
78
|
|
|
82
79
|
return rv
|
|
83
80
|
|
|
@@ -87,35 +84,46 @@ class RqIntegration(Integration):
|
|
|
87
84
|
|
|
88
85
|
def sentry_patched_handle_exception(self, job, *exc_info, **kwargs):
|
|
89
86
|
# type: (Worker, Any, *Any, **Any) -> Any
|
|
90
|
-
|
|
87
|
+
retry = (
|
|
88
|
+
hasattr(job, "retries_left")
|
|
89
|
+
and job.retries_left
|
|
90
|
+
and job.retries_left > 0
|
|
91
|
+
)
|
|
92
|
+
failed = job._status == JobStatus.FAILED or job.is_failed
|
|
93
|
+
if failed and not retry:
|
|
94
|
+
_capture_exception(exc_info)
|
|
95
|
+
|
|
91
96
|
return old_handle_exception(self, job, *exc_info, **kwargs)
|
|
92
97
|
|
|
93
98
|
Worker.handle_exception = sentry_patched_handle_exception
|
|
94
99
|
|
|
95
100
|
old_enqueue_job = Queue.enqueue_job
|
|
96
101
|
|
|
102
|
+
@ensure_integration_enabled(RqIntegration, old_enqueue_job)
|
|
97
103
|
def sentry_patched_enqueue_job(self, job, **kwargs):
|
|
98
104
|
# type: (Queue, Any, **Any) -> Any
|
|
99
|
-
|
|
100
|
-
if
|
|
105
|
+
scope = sentry_sdk.get_current_scope()
|
|
106
|
+
if scope.span is not None:
|
|
101
107
|
job.meta["_sentry_trace_headers"] = dict(
|
|
102
|
-
|
|
108
|
+
scope.iter_trace_propagation_headers()
|
|
103
109
|
)
|
|
104
110
|
|
|
105
111
|
return old_enqueue_job(self, job, **kwargs)
|
|
106
112
|
|
|
107
113
|
Queue.enqueue_job = sentry_patched_enqueue_job
|
|
108
114
|
|
|
115
|
+
ignore_logger("rq.worker")
|
|
116
|
+
|
|
109
117
|
|
|
110
118
|
def _make_event_processor(weak_job):
|
|
111
119
|
# type: (Callable[[], Job]) -> EventProcessor
|
|
112
120
|
def event_processor(event, hint):
|
|
113
|
-
# type: (
|
|
121
|
+
# type: (Event, dict[str, Any]) -> Event
|
|
114
122
|
job = weak_job()
|
|
115
123
|
if job is not None:
|
|
116
124
|
with capture_internal_exceptions():
|
|
117
125
|
extra = event.setdefault("extra", {})
|
|
118
|
-
|
|
126
|
+
rq_job = {
|
|
119
127
|
"job_id": job.id,
|
|
120
128
|
"func": job.func_name,
|
|
121
129
|
"args": job.args,
|
|
@@ -123,6 +131,13 @@ def _make_event_processor(weak_job):
|
|
|
123
131
|
"description": job.description,
|
|
124
132
|
}
|
|
125
133
|
|
|
134
|
+
if job.enqueued_at:
|
|
135
|
+
rq_job["enqueued_at"] = format_timestamp(job.enqueued_at)
|
|
136
|
+
if job.started_at:
|
|
137
|
+
rq_job["started_at"] = format_timestamp(job.started_at)
|
|
138
|
+
|
|
139
|
+
extra["rq-job"] = rq_job
|
|
140
|
+
|
|
126
141
|
if "exc_info" in hint:
|
|
127
142
|
with capture_internal_exceptions():
|
|
128
143
|
if issubclass(hint["exc_info"][0], JobTimeoutException):
|
|
@@ -135,12 +150,7 @@ def _make_event_processor(weak_job):
|
|
|
135
150
|
|
|
136
151
|
def _capture_exception(exc_info, **kwargs):
|
|
137
152
|
# type: (ExcInfo, **Any) -> None
|
|
138
|
-
|
|
139
|
-
if hub.get_integration(RqIntegration) is None:
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
# If an integration is there, a client has to be there.
|
|
143
|
-
client = hub.client # type: Any
|
|
153
|
+
client = sentry_sdk.get_client()
|
|
144
154
|
|
|
145
155
|
event, hint = event_from_exception(
|
|
146
156
|
exc_info,
|
|
@@ -148,4 +158,4 @@ def _capture_exception(exc_info, **kwargs):
|
|
|
148
158
|
mechanism={"type": "rq", "handled": False},
|
|
149
159
|
)
|
|
150
160
|
|
|
151
|
-
|
|
161
|
+
sentry_sdk.capture_event(event, hint=hint)
|