From b7adeb52bf6bd9d46e7bc044ca59108c3363797c Mon Sep 17 00:00:00 2001 From: Zhenay Date: Wed, 1 Mar 2023 17:57:48 +0300 Subject: [PATCH 01/19] Add support for redis asyncio --- sentry_sdk/integrations/redis.py | 201 ++++++++++++++++++------- tests/integrations/redis/test_redis.py | 71 ++++++++- tox.ini | 3 +- 3 files changed, 218 insertions(+), 57 deletions(-) diff --git a/sentry_sdk/integrations/redis.py b/sentry_sdk/integrations/redis.py index aae5647f3d..44bc3ec765 100644 --- a/sentry_sdk/integrations/redis.py +++ b/sentry_sdk/integrations/redis.py @@ -9,6 +9,7 @@ if MYPY: from typing import Any, Sequence + from sentry_sdk.tracing import Span _SINGLE_KEY_COMMANDS = frozenset( ["decr", "decrby", "get", "incr", "incrby", "pttl", "set", "setex", "setnx", "ttl"] @@ -19,6 +20,31 @@ _MAX_NUM_ARGS = 10 +def _set_pipeline_data( + span, is_cluster, get_command_args_fn, is_transaction, command_stack +): + # type: (Span, bool, Any, bool, Sequence[Any]) -> None + span.set_tag("redis.is_cluster", is_cluster) + transaction = is_transaction if not is_cluster else False + span.set_tag("redis.transaction", transaction) + + commands = [] + for i, arg in enumerate(command_stack): + if i > _MAX_NUM_ARGS: + break + command_args = [] + for j, command_arg in enumerate(get_command_args_fn(arg)): + if j > 0: + command_arg = repr(command_arg) + command_args.append(command_arg) + commands.append(" ".join(command_args)) + + span.set_data( + "redis.commands", + {"count": len(command_stack), "first_ten": commands}, + ) + + def patch_redis_pipeline(pipeline_cls, is_cluster, get_command_args_fn): # type: (Any, bool, Any) -> None old_execute = pipeline_cls.execute @@ -34,24 +60,12 @@ def sentry_patched_execute(self, *args, **kwargs): op=OP.DB_REDIS, description="redis.pipeline.execute" ) as span: with capture_internal_exceptions(): - span.set_tag("redis.is_cluster", is_cluster) - transaction = self.transaction if not is_cluster else False - span.set_tag("redis.transaction", transaction) - - commands = [] - for i, arg in enumerate(self.command_stack): - if i > _MAX_NUM_ARGS: - break - command_args = [] - for j, command_arg in enumerate(get_command_args_fn(arg)): - if j > 0: - command_arg = repr(command_arg) - command_args.append(command_arg) - commands.append(" ".join(command_args)) - - span.set_data( - "redis.commands", - {"count": len(self.command_stack), "first_ten": commands}, + _set_pipeline_data( + span, + is_cluster, + get_command_args_fn, + self.transaction, + self.command_stack, ) return old_execute(self, *args, **kwargs) @@ -59,6 +73,34 @@ def sentry_patched_execute(self, *args, **kwargs): pipeline_cls.execute = sentry_patched_execute +def patch_redis_async_pipeline(pipeline_cls): + # type: (Any) -> None + old_execute = pipeline_cls.execute + + async def _sentry_execute(self, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + hub = Hub.current + + if hub.get_integration(RedisIntegration) is None: + return await old_execute(self, *args, **kwargs) + + with hub.start_span( + op=OP.DB_REDIS, description="redis.pipeline.execute" + ) as span: + with capture_internal_exceptions(): + _set_pipeline_data( + span, + False, + _get_redis_command_args, + self.is_transaction, + self.command_stack, + ) + + return await old_execute(self, *args, **kwargs) + + pipeline_cls.execute = _sentry_execute + + def _get_redis_command_args(command): # type: (Any) -> Sequence[Any] return command[0] @@ -69,6 +111,37 @@ def _parse_rediscluster_command(command): return command.args +def _patch_redis(redis): + # type: (Any) -> None + patch_redis_client(redis.StrictRedis, is_cluster=False) + patch_redis_pipeline(redis.client.Pipeline, False, _get_redis_command_args) + try: + strict_pipeline = redis.client.StrictPipeline + except AttributeError: + pass + else: + patch_redis_pipeline(strict_pipeline, False, _get_redis_command_args) + try: + import redis.asyncio # type: ignore + except ImportError: + pass + else: + patch_redis_async_client(redis.asyncio.client.StrictRedis) + patch_redis_async_pipeline(redis.asyncio.client.Pipeline) + + +def _patch_rb(): + # type: () -> None + try: + import rb.clients # type: ignore + except ImportError: + pass + else: + patch_redis_client(rb.clients.FanoutClient, is_cluster=False) + patch_redis_client(rb.clients.MappingClient, is_cluster=False) + patch_redis_client(rb.clients.RoutingClient, is_cluster=False) + + def _patch_rediscluster(): # type: () -> None try: @@ -104,23 +177,8 @@ def setup_once(): except ImportError: raise DidNotEnable("Redis client not installed") - patch_redis_client(redis.StrictRedis, is_cluster=False) - patch_redis_pipeline(redis.client.Pipeline, False, _get_redis_command_args) - try: - strict_pipeline = redis.client.StrictPipeline # type: ignore - except AttributeError: - pass - else: - patch_redis_pipeline(strict_pipeline, False, _get_redis_command_args) - - try: - import rb.clients # type: ignore - except ImportError: - pass - else: - patch_redis_client(rb.clients.FanoutClient, is_cluster=False) - patch_redis_client(rb.clients.MappingClient, is_cluster=False) - patch_redis_client(rb.clients.RoutingClient, is_cluster=False) + _patch_redis(redis) + _patch_rb() try: _patch_rediscluster() @@ -128,6 +186,37 @@ def setup_once(): logger.exception("Error occurred while patching `rediscluster` library") +def _get_span_description(name, *args): + # type: (str, *Any) -> str + description = name + + with capture_internal_exceptions(): + description_parts = [name] + for i, arg in enumerate(args): + if i > _MAX_NUM_ARGS: + break + + description_parts.append(repr(arg)) + + description = " ".join(description_parts) + + return description + + +def _set_client_data(span, is_cluster, name, *args): + # type: (Span, bool, str, *Any) -> None + span.set_tag("redis.is_cluster", is_cluster) + if name: + span.set_tag("redis.command", name) + + if name and args: + name_low = name.lower() + if (name_low in _SINGLE_KEY_COMMANDS) or ( + name_low in _MULTI_KEY_COMMANDS and len(args) == 1 + ): + span.set_tag("redis.key", args[0]) + + def patch_redis_client(cls, is_cluster): # type: (Any, bool) -> None """ @@ -143,30 +232,32 @@ def sentry_patched_execute_command(self, name, *args, **kwargs): if hub.get_integration(RedisIntegration) is None: return old_execute_command(self, name, *args, **kwargs) - description = name + description = _get_span_description(name, *args) - with capture_internal_exceptions(): - description_parts = [name] - for i, arg in enumerate(args): - if i > _MAX_NUM_ARGS: - break + with hub.start_span(op=OP.DB_REDIS, description=description) as span: + _set_client_data(span, is_cluster, name, *args) - description_parts.append(repr(arg)) + return old_execute_command(self, name, *args, **kwargs) - description = " ".join(description_parts) + cls.execute_command = sentry_patched_execute_command - with hub.start_span(op=OP.DB_REDIS, description=description) as span: - span.set_tag("redis.is_cluster", is_cluster) - if name: - span.set_tag("redis.command", name) - if name and args: - name_low = name.lower() - if (name_low in _SINGLE_KEY_COMMANDS) or ( - name_low in _MULTI_KEY_COMMANDS and len(args) == 1 - ): - span.set_tag("redis.key", args[0]) +def patch_redis_async_client(cls): + # type: (Any) -> None + old_execute_command = cls.execute_command - return old_execute_command(self, name, *args, **kwargs) + async def _sentry_execute_command(self, name, *args, **kwargs): + # type: (Any, str, *Any, **Any) -> Any + hub = Hub.current - cls.execute_command = sentry_patched_execute_command + if hub.get_integration(RedisIntegration) is None: + return await old_execute_command(self, name, *args, **kwargs) + + description = _get_span_description(name, *args) + + with hub.start_span(op=OP.DB_REDIS, description=description) as span: + _set_client_data(span, False, name, *args) + + return await old_execute_command(self, name, *args, **kwargs) + + cls.execute_command = _sentry_execute_command diff --git a/tests/integrations/redis/test_redis.py b/tests/integrations/redis/test_redis.py index 9a6d066e03..8f092613f6 100644 --- a/tests/integrations/redis/test_redis.py +++ b/tests/integrations/redis/test_redis.py @@ -1,8 +1,14 @@ +import pytest + from sentry_sdk import capture_message, start_transaction from sentry_sdk.integrations.redis import RedisIntegration from fakeredis import FakeStrictRedis -import pytest + +try: + from fakeredis.aioredis import FakeRedis as AsyncFakeRedis +except ImportError: + AsyncFakeRedis = None def test_basic(sentry_init, capture_events): @@ -58,3 +64,66 @@ def test_redis_pipeline(sentry_init, capture_events, is_transaction): "redis.transaction": is_transaction, "redis.is_cluster": False, } + + +@pytest.mark.asyncio +@pytest.mark.skipif( + AsyncFakeRedis is None, reason="fakeredis.asyncio is not available." +) +async def test_async_basic(sentry_init, capture_events): + sentry_init(integrations=[RedisIntegration()]) + events = capture_events() + + connection = AsyncFakeRedis() + + await connection.get("foobar") + capture_message("hi") + + (event,) = events + (crumb,) = event["breadcrumbs"]["values"] + + assert crumb == { + "category": "redis", + "message": "GET 'foobar'", + "data": { + "redis.key": "foobar", + "redis.command": "GET", + "redis.is_cluster": False, + }, + "timestamp": crumb["timestamp"], + "type": "redis", + } + + +@pytest.mark.parametrize("is_transaction", [False, True]) +@pytest.mark.asyncio +@pytest.mark.skipif( + AsyncFakeRedis is None, reason="fakeredis.asyncio is not available." +) +async def test_async_redis_pipeline(sentry_init, capture_events, is_transaction): + sentry_init(integrations=[RedisIntegration()], traces_sample_rate=1.0) + events = capture_events() + + connection = FakeStrictRedis() + with start_transaction(): + + pipeline = connection.pipeline(transaction=is_transaction) + pipeline.get("foo") + pipeline.set("bar", 1) + pipeline.set("baz", 2) + pipeline.execute() + + (event,) = events + (span,) = event["spans"] + assert span["op"] == "db.redis" + assert span["description"] == "redis.pipeline.execute" + assert span["data"] == { + "redis.commands": { + "count": 3, + "first_ten": ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"], + } + } + assert span["tags"] == { + "redis.transaction": is_transaction, + "redis.is_cluster": False, + } diff --git a/tox.ini b/tox.ini index 45facf42c0..4e8d4f7d04 100644 --- a/tox.ini +++ b/tox.ini @@ -333,7 +333,8 @@ deps = requests: requests>=2.0 # Redis - redis: fakeredis<1.7.4 + redis: fakeredis>=1.7.5 + redis: pytest-asyncio # Redis Cluster rediscluster-v1: redis-py-cluster>=1.0.0,<2.0.0 From 4d5c009d59a199cc198bc22485f3eaa50ac73079 Mon Sep 17 00:00:00 2001 From: Evgeny Seregin Date: Wed, 1 Mar 2023 18:56:03 +0300 Subject: [PATCH 02/19] python 2.7 --- .../integrations/{redis.py => old_redis.py} | 0 sentry_sdk/integrations/redis/__init__.py | 219 ++++++++++++++++++ sentry_sdk/integrations/redis/asyncio.py | 67 ++++++ tests/integrations/redis/asyncio/__init__.py | 3 + .../integrations/redis/asyncio/test_redis.py | 63 +++++ tests/integrations/redis/test_redis.py | 68 ------ 6 files changed, 352 insertions(+), 68 deletions(-) rename sentry_sdk/integrations/{redis.py => old_redis.py} (100%) create mode 100644 sentry_sdk/integrations/redis/__init__.py create mode 100644 sentry_sdk/integrations/redis/asyncio.py create mode 100644 tests/integrations/redis/asyncio/__init__.py create mode 100644 tests/integrations/redis/asyncio/test_redis.py diff --git a/sentry_sdk/integrations/redis.py b/sentry_sdk/integrations/old_redis.py similarity index 100% rename from sentry_sdk/integrations/redis.py rename to sentry_sdk/integrations/old_redis.py diff --git a/sentry_sdk/integrations/redis/__init__.py b/sentry_sdk/integrations/redis/__init__.py new file mode 100644 index 0000000000..94caf5065b --- /dev/null +++ b/sentry_sdk/integrations/redis/__init__.py @@ -0,0 +1,219 @@ +from __future__ import absolute_import + +from sentry_sdk import Hub +from sentry_sdk.consts import OP +from sentry_sdk.utils import capture_internal_exceptions, logger +from sentry_sdk.integrations import Integration, DidNotEnable + +from sentry_sdk._types import MYPY + +if MYPY: + from typing import Any, Sequence + from sentry_sdk.tracing import Span + +_SINGLE_KEY_COMMANDS = frozenset( + ["decr", "decrby", "get", "incr", "incrby", "pttl", "set", "setex", "setnx", "ttl"] +) +_MULTI_KEY_COMMANDS = frozenset(["del", "touch", "unlink"]) + +#: Trim argument lists to this many values +_MAX_NUM_ARGS = 10 + + +def _set_pipeline_data( + span, is_cluster, get_command_args_fn, is_transaction, command_stack +): + # type: (Span, bool, Any, bool, Sequence[Any]) -> None + span.set_tag("redis.is_cluster", is_cluster) + transaction = is_transaction if not is_cluster else False + span.set_tag("redis.transaction", transaction) + + commands = [] + for i, arg in enumerate(command_stack): + if i > _MAX_NUM_ARGS: + break + command_args = [] + for j, command_arg in enumerate(get_command_args_fn(arg)): + if j > 0: + command_arg = repr(command_arg) + command_args.append(command_arg) + commands.append(" ".join(command_args)) + + span.set_data( + "redis.commands", + {"count": len(command_stack), "first_ten": commands}, + ) + + +def patch_redis_pipeline(pipeline_cls, is_cluster, get_command_args_fn): + # type: (Any, bool, Any) -> None + old_execute = pipeline_cls.execute + + def sentry_patched_execute(self, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + hub = Hub.current + + if hub.get_integration(RedisIntegration) is None: + return old_execute(self, *args, **kwargs) + + with hub.start_span( + op=OP.DB_REDIS, description="redis.pipeline.execute" + ) as span: + with capture_internal_exceptions(): + _set_pipeline_data( + span, + is_cluster, + get_command_args_fn, + self.transaction, + self.command_stack, + ) + + return old_execute(self, *args, **kwargs) + + pipeline_cls.execute = sentry_patched_execute + + +def _get_redis_command_args(command): + # type: (Any) -> Sequence[Any] + return command[0] + + +def _parse_rediscluster_command(command): + # type: (Any) -> Sequence[Any] + return command.args + + +def _patch_redis(redis): + # type: (Any) -> None + patch_redis_client(redis.StrictRedis, is_cluster=False) + patch_redis_pipeline(redis.client.Pipeline, False, _get_redis_command_args) + try: + strict_pipeline = redis.client.StrictPipeline + except AttributeError: + pass + else: + patch_redis_pipeline(strict_pipeline, False, _get_redis_command_args) + try: + import redis.asyncio # type: ignore + except ImportError: + pass + else: + from sentry_sdk.integrations.redis.asyncio import ( + patch_redis_async_client, + patch_redis_async_pipeline, + ) + + patch_redis_async_client(redis.asyncio.client.StrictRedis) + patch_redis_async_pipeline(redis.asyncio.client.Pipeline) + + +def _patch_rb(): + # type: () -> None + try: + import rb.clients # type: ignore + except ImportError: + pass + else: + patch_redis_client(rb.clients.FanoutClient, is_cluster=False) + patch_redis_client(rb.clients.MappingClient, is_cluster=False) + patch_redis_client(rb.clients.RoutingClient, is_cluster=False) + + +def _patch_rediscluster(): + # type: () -> None + try: + import rediscluster # type: ignore + except ImportError: + return + + patch_redis_client(rediscluster.RedisCluster, is_cluster=True) + + # up to v1.3.6, __version__ attribute is a tuple + # from v2.0.0, __version__ is a string and VERSION a tuple + version = getattr(rediscluster, "VERSION", rediscluster.__version__) + + # StrictRedisCluster was introduced in v0.2.0 and removed in v2.0.0 + # https://github.com/Grokzen/redis-py-cluster/blob/master/docs/release-notes.rst + if (0, 2, 0) < version < (2, 0, 0): + pipeline_cls = rediscluster.pipeline.StrictClusterPipeline + patch_redis_client(rediscluster.StrictRedisCluster, is_cluster=True) + else: + pipeline_cls = rediscluster.pipeline.ClusterPipeline + + patch_redis_pipeline(pipeline_cls, True, _parse_rediscluster_command) + + +class RedisIntegration(Integration): + identifier = "redis" + + @staticmethod + def setup_once(): + # type: () -> None + try: + import redis + except ImportError: + raise DidNotEnable("Redis client not installed") + + _patch_redis(redis) + _patch_rb() + + try: + _patch_rediscluster() + except Exception: + logger.exception("Error occurred while patching `rediscluster` library") + + +def _get_span_description(name, *args): + # type: (str, *Any) -> str + description = name + + with capture_internal_exceptions(): + description_parts = [name] + for i, arg in enumerate(args): + if i > _MAX_NUM_ARGS: + break + + description_parts.append(repr(arg)) + + description = " ".join(description_parts) + + return description + + +def _set_client_data(span, is_cluster, name, *args): + # type: (Span, bool, str, *Any) -> None + span.set_tag("redis.is_cluster", is_cluster) + if name: + span.set_tag("redis.command", name) + + if name and args: + name_low = name.lower() + if (name_low in _SINGLE_KEY_COMMANDS) or ( + name_low in _MULTI_KEY_COMMANDS and len(args) == 1 + ): + span.set_tag("redis.key", args[0]) + + +def patch_redis_client(cls, is_cluster): + # type: (Any, bool) -> None + """ + This function can be used to instrument custom redis client classes or + subclasses. + """ + old_execute_command = cls.execute_command + + def sentry_patched_execute_command(self, name, *args, **kwargs): + # type: (Any, str, *Any, **Any) -> Any + hub = Hub.current + + if hub.get_integration(RedisIntegration) is None: + return old_execute_command(self, name, *args, **kwargs) + + description = _get_span_description(name, *args) + + with hub.start_span(op=OP.DB_REDIS, description=description) as span: + _set_client_data(span, is_cluster, name, *args) + + return old_execute_command(self, name, *args, **kwargs) + + cls.execute_command = sentry_patched_execute_command diff --git a/sentry_sdk/integrations/redis/asyncio.py b/sentry_sdk/integrations/redis/asyncio.py new file mode 100644 index 0000000000..d0e4e16a87 --- /dev/null +++ b/sentry_sdk/integrations/redis/asyncio.py @@ -0,0 +1,67 @@ +from __future__ import absolute_import + +from sentry_sdk import Hub +from sentry_sdk.consts import OP +from sentry_sdk.utils import capture_internal_exceptions +from sentry_sdk.integrations.redis import ( + RedisIntegration, + _get_redis_command_args, + _get_span_description, + _set_client_data, + _set_pipeline_data, +) + + +from sentry_sdk._types import MYPY + +if MYPY: + from typing import Any + + +def patch_redis_async_pipeline(pipeline_cls): + # type: (Any) -> None + old_execute = pipeline_cls.execute + + async def _sentry_execute(self, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + hub = Hub.current + + if hub.get_integration(RedisIntegration) is None: + return await old_execute(self, *args, **kwargs) + + with hub.start_span( + op=OP.DB_REDIS, description="redis.pipeline.execute" + ) as span: + with capture_internal_exceptions(): + _set_pipeline_data( + span, + False, + _get_redis_command_args, + self.is_transaction, + self.command_stack, + ) + + return await old_execute(self, *args, **kwargs) + + pipeline_cls.execute = _sentry_execute + + +def patch_redis_async_client(cls): + # type: (Any) -> None + old_execute_command = cls.execute_command + + async def _sentry_execute_command(self, name, *args, **kwargs): + # type: (Any, str, *Any, **Any) -> Any + hub = Hub.current + + if hub.get_integration(RedisIntegration) is None: + return await old_execute_command(self, name, *args, **kwargs) + + description = _get_span_description(name, *args) + + with hub.start_span(op=OP.DB_REDIS, description=description) as span: + _set_client_data(span, False, name, *args) + + return await old_execute_command(self, name, *args, **kwargs) + + cls.execute_command = _sentry_execute_command diff --git a/tests/integrations/redis/asyncio/__init__.py b/tests/integrations/redis/asyncio/__init__.py new file mode 100644 index 0000000000..bd93246a9a --- /dev/null +++ b/tests/integrations/redis/asyncio/__init__.py @@ -0,0 +1,3 @@ +import pytest + +pytest.importorskip("fakeredis.aioredis") diff --git a/tests/integrations/redis/asyncio/test_redis.py b/tests/integrations/redis/asyncio/test_redis.py new file mode 100644 index 0000000000..fac01c6146 --- /dev/null +++ b/tests/integrations/redis/asyncio/test_redis.py @@ -0,0 +1,63 @@ +import pytest + +from sentry_sdk import capture_message, start_transaction +from sentry_sdk.integrations.redis import RedisIntegration + +from fakeredis.aioredis import FakeRedis + + +@pytest.mark.asyncio +async def test_async_basic(sentry_init, capture_events): + sentry_init(integrations=[RedisIntegration()]) + events = capture_events() + + connection = FakeRedis() + + await connection.get("foobar") + capture_message("hi") + + (event,) = events + (crumb,) = event["breadcrumbs"]["values"] + + assert crumb == { + "category": "redis", + "message": "GET 'foobar'", + "data": { + "redis.key": "foobar", + "redis.command": "GET", + "redis.is_cluster": False, + }, + "timestamp": crumb["timestamp"], + "type": "redis", + } + + +@pytest.mark.parametrize("is_transaction", [False, True]) +@pytest.mark.asyncio +async def test_async_redis_pipeline(sentry_init, capture_events, is_transaction): + sentry_init(integrations=[RedisIntegration()], traces_sample_rate=1.0) + events = capture_events() + + connection = FakeRedis() + with start_transaction(): + + pipeline = connection.pipeline(transaction=is_transaction) + pipeline.get("foo") + pipeline.set("bar", 1) + pipeline.set("baz", 2) + await pipeline.execute() + + (event,) = events + (span,) = event["spans"] + assert span["op"] == "db.redis" + assert span["description"] == "redis.pipeline.execute" + assert span["data"] == { + "redis.commands": { + "count": 3, + "first_ten": ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"], + } + } + assert span["tags"] == { + "redis.transaction": is_transaction, + "redis.is_cluster": False, + } diff --git a/tests/integrations/redis/test_redis.py b/tests/integrations/redis/test_redis.py index 8f092613f6..75d5aac5ac 100644 --- a/tests/integrations/redis/test_redis.py +++ b/tests/integrations/redis/test_redis.py @@ -5,11 +5,6 @@ from fakeredis import FakeStrictRedis -try: - from fakeredis.aioredis import FakeRedis as AsyncFakeRedis -except ImportError: - AsyncFakeRedis = None - def test_basic(sentry_init, capture_events): sentry_init(integrations=[RedisIntegration()]) @@ -64,66 +59,3 @@ def test_redis_pipeline(sentry_init, capture_events, is_transaction): "redis.transaction": is_transaction, "redis.is_cluster": False, } - - -@pytest.mark.asyncio -@pytest.mark.skipif( - AsyncFakeRedis is None, reason="fakeredis.asyncio is not available." -) -async def test_async_basic(sentry_init, capture_events): - sentry_init(integrations=[RedisIntegration()]) - events = capture_events() - - connection = AsyncFakeRedis() - - await connection.get("foobar") - capture_message("hi") - - (event,) = events - (crumb,) = event["breadcrumbs"]["values"] - - assert crumb == { - "category": "redis", - "message": "GET 'foobar'", - "data": { - "redis.key": "foobar", - "redis.command": "GET", - "redis.is_cluster": False, - }, - "timestamp": crumb["timestamp"], - "type": "redis", - } - - -@pytest.mark.parametrize("is_transaction", [False, True]) -@pytest.mark.asyncio -@pytest.mark.skipif( - AsyncFakeRedis is None, reason="fakeredis.asyncio is not available." -) -async def test_async_redis_pipeline(sentry_init, capture_events, is_transaction): - sentry_init(integrations=[RedisIntegration()], traces_sample_rate=1.0) - events = capture_events() - - connection = FakeStrictRedis() - with start_transaction(): - - pipeline = connection.pipeline(transaction=is_transaction) - pipeline.get("foo") - pipeline.set("bar", 1) - pipeline.set("baz", 2) - pipeline.execute() - - (event,) = events - (span,) = event["spans"] - assert span["op"] == "db.redis" - assert span["description"] == "redis.pipeline.execute" - assert span["data"] == { - "redis.commands": { - "count": 3, - "first_ten": ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"], - } - } - assert span["tags"] == { - "redis.transaction": is_transaction, - "redis.is_cluster": False, - } From 24a420aeeef1d10b52c1757fc717e5ac143d4cf3 Mon Sep 17 00:00:00 2001 From: Zhenay Date: Wed, 1 Mar 2023 18:58:50 +0300 Subject: [PATCH 03/19] Clean garbage --- sentry_sdk/integrations/old_redis.py | 263 --------------------------- 1 file changed, 263 deletions(-) delete mode 100644 sentry_sdk/integrations/old_redis.py diff --git a/sentry_sdk/integrations/old_redis.py b/sentry_sdk/integrations/old_redis.py deleted file mode 100644 index 44bc3ec765..0000000000 --- a/sentry_sdk/integrations/old_redis.py +++ /dev/null @@ -1,263 +0,0 @@ -from __future__ import absolute_import - -from sentry_sdk import Hub -from sentry_sdk.consts import OP -from sentry_sdk.utils import capture_internal_exceptions, logger -from sentry_sdk.integrations import Integration, DidNotEnable - -from sentry_sdk._types import MYPY - -if MYPY: - from typing import Any, Sequence - from sentry_sdk.tracing import Span - -_SINGLE_KEY_COMMANDS = frozenset( - ["decr", "decrby", "get", "incr", "incrby", "pttl", "set", "setex", "setnx", "ttl"] -) -_MULTI_KEY_COMMANDS = frozenset(["del", "touch", "unlink"]) - -#: Trim argument lists to this many values -_MAX_NUM_ARGS = 10 - - -def _set_pipeline_data( - span, is_cluster, get_command_args_fn, is_transaction, command_stack -): - # type: (Span, bool, Any, bool, Sequence[Any]) -> None - span.set_tag("redis.is_cluster", is_cluster) - transaction = is_transaction if not is_cluster else False - span.set_tag("redis.transaction", transaction) - - commands = [] - for i, arg in enumerate(command_stack): - if i > _MAX_NUM_ARGS: - break - command_args = [] - for j, command_arg in enumerate(get_command_args_fn(arg)): - if j > 0: - command_arg = repr(command_arg) - command_args.append(command_arg) - commands.append(" ".join(command_args)) - - span.set_data( - "redis.commands", - {"count": len(command_stack), "first_ten": commands}, - ) - - -def patch_redis_pipeline(pipeline_cls, is_cluster, get_command_args_fn): - # type: (Any, bool, Any) -> None - old_execute = pipeline_cls.execute - - def sentry_patched_execute(self, *args, **kwargs): - # type: (Any, *Any, **Any) -> Any - hub = Hub.current - - if hub.get_integration(RedisIntegration) is None: - return old_execute(self, *args, **kwargs) - - with hub.start_span( - op=OP.DB_REDIS, description="redis.pipeline.execute" - ) as span: - with capture_internal_exceptions(): - _set_pipeline_data( - span, - is_cluster, - get_command_args_fn, - self.transaction, - self.command_stack, - ) - - return old_execute(self, *args, **kwargs) - - pipeline_cls.execute = sentry_patched_execute - - -def patch_redis_async_pipeline(pipeline_cls): - # type: (Any) -> None - old_execute = pipeline_cls.execute - - async def _sentry_execute(self, *args, **kwargs): - # type: (Any, *Any, **Any) -> Any - hub = Hub.current - - if hub.get_integration(RedisIntegration) is None: - return await old_execute(self, *args, **kwargs) - - with hub.start_span( - op=OP.DB_REDIS, description="redis.pipeline.execute" - ) as span: - with capture_internal_exceptions(): - _set_pipeline_data( - span, - False, - _get_redis_command_args, - self.is_transaction, - self.command_stack, - ) - - return await old_execute(self, *args, **kwargs) - - pipeline_cls.execute = _sentry_execute - - -def _get_redis_command_args(command): - # type: (Any) -> Sequence[Any] - return command[0] - - -def _parse_rediscluster_command(command): - # type: (Any) -> Sequence[Any] - return command.args - - -def _patch_redis(redis): - # type: (Any) -> None - patch_redis_client(redis.StrictRedis, is_cluster=False) - patch_redis_pipeline(redis.client.Pipeline, False, _get_redis_command_args) - try: - strict_pipeline = redis.client.StrictPipeline - except AttributeError: - pass - else: - patch_redis_pipeline(strict_pipeline, False, _get_redis_command_args) - try: - import redis.asyncio # type: ignore - except ImportError: - pass - else: - patch_redis_async_client(redis.asyncio.client.StrictRedis) - patch_redis_async_pipeline(redis.asyncio.client.Pipeline) - - -def _patch_rb(): - # type: () -> None - try: - import rb.clients # type: ignore - except ImportError: - pass - else: - patch_redis_client(rb.clients.FanoutClient, is_cluster=False) - patch_redis_client(rb.clients.MappingClient, is_cluster=False) - patch_redis_client(rb.clients.RoutingClient, is_cluster=False) - - -def _patch_rediscluster(): - # type: () -> None - try: - import rediscluster # type: ignore - except ImportError: - return - - patch_redis_client(rediscluster.RedisCluster, is_cluster=True) - - # up to v1.3.6, __version__ attribute is a tuple - # from v2.0.0, __version__ is a string and VERSION a tuple - version = getattr(rediscluster, "VERSION", rediscluster.__version__) - - # StrictRedisCluster was introduced in v0.2.0 and removed in v2.0.0 - # https://github.com/Grokzen/redis-py-cluster/blob/master/docs/release-notes.rst - if (0, 2, 0) < version < (2, 0, 0): - pipeline_cls = rediscluster.pipeline.StrictClusterPipeline - patch_redis_client(rediscluster.StrictRedisCluster, is_cluster=True) - else: - pipeline_cls = rediscluster.pipeline.ClusterPipeline - - patch_redis_pipeline(pipeline_cls, True, _parse_rediscluster_command) - - -class RedisIntegration(Integration): - identifier = "redis" - - @staticmethod - def setup_once(): - # type: () -> None - try: - import redis - except ImportError: - raise DidNotEnable("Redis client not installed") - - _patch_redis(redis) - _patch_rb() - - try: - _patch_rediscluster() - except Exception: - logger.exception("Error occurred while patching `rediscluster` library") - - -def _get_span_description(name, *args): - # type: (str, *Any) -> str - description = name - - with capture_internal_exceptions(): - description_parts = [name] - for i, arg in enumerate(args): - if i > _MAX_NUM_ARGS: - break - - description_parts.append(repr(arg)) - - description = " ".join(description_parts) - - return description - - -def _set_client_data(span, is_cluster, name, *args): - # type: (Span, bool, str, *Any) -> None - span.set_tag("redis.is_cluster", is_cluster) - if name: - span.set_tag("redis.command", name) - - if name and args: - name_low = name.lower() - if (name_low in _SINGLE_KEY_COMMANDS) or ( - name_low in _MULTI_KEY_COMMANDS and len(args) == 1 - ): - span.set_tag("redis.key", args[0]) - - -def patch_redis_client(cls, is_cluster): - # type: (Any, bool) -> None - """ - This function can be used to instrument custom redis client classes or - subclasses. - """ - old_execute_command = cls.execute_command - - def sentry_patched_execute_command(self, name, *args, **kwargs): - # type: (Any, str, *Any, **Any) -> Any - hub = Hub.current - - if hub.get_integration(RedisIntegration) is None: - return old_execute_command(self, name, *args, **kwargs) - - description = _get_span_description(name, *args) - - with hub.start_span(op=OP.DB_REDIS, description=description) as span: - _set_client_data(span, is_cluster, name, *args) - - return old_execute_command(self, name, *args, **kwargs) - - cls.execute_command = sentry_patched_execute_command - - -def patch_redis_async_client(cls): - # type: (Any) -> None - old_execute_command = cls.execute_command - - async def _sentry_execute_command(self, name, *args, **kwargs): - # type: (Any, str, *Any, **Any) -> Any - hub = Hub.current - - if hub.get_integration(RedisIntegration) is None: - return await old_execute_command(self, name, *args, **kwargs) - - description = _get_span_description(name, *args) - - with hub.start_span(op=OP.DB_REDIS, description=description) as span: - _set_client_data(span, False, name, *args) - - return await old_execute_command(self, name, *args, **kwargs) - - cls.execute_command = _sentry_execute_command From 7539af7abf6e6456f9e8f5207999bee00a77667d Mon Sep 17 00:00:00 2001 From: Evgeny Seregin Date: Wed, 1 Mar 2023 19:09:45 +0300 Subject: [PATCH 04/19] Change version of fakeredis --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4e8d4f7d04..3176cfbbee 100644 --- a/tox.ini +++ b/tox.ini @@ -333,7 +333,7 @@ deps = requests: requests>=2.0 # Redis - redis: fakeredis>=1.7.5 + redis: fakeredis!=1.7.4 redis: pytest-asyncio # Redis Cluster From 27d605e6e0fa0ab921af2c0f94a33f1164ea3a48 Mon Sep 17 00:00:00 2001 From: Zhenay Date: Wed, 1 Mar 2023 19:30:15 +0300 Subject: [PATCH 05/19] Install pytest-asyncio only for python3 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3176cfbbee..c6246a9abe 100644 --- a/tox.ini +++ b/tox.ini @@ -334,7 +334,7 @@ deps = # Redis redis: fakeredis!=1.7.4 - redis: pytest-asyncio + {py3.7,py3.8,py3.9}-redis: pytest-asyncio # Redis Cluster rediscluster-v1: redis-py-cluster>=1.0.0,<2.0.0 From 582c832b0fd5837ce6feca36a05b53bd1a6ff847 Mon Sep 17 00:00:00 2001 From: Zhenay Date: Fri, 17 Mar 2023 19:30:33 +0300 Subject: [PATCH 06/19] Add py3.10 and py3.11 in test matrix --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index c9911399ee..d4e9d87bda 100644 --- a/tox.ini +++ b/tox.ini @@ -114,7 +114,7 @@ envlist = {py3.7,py3.8,py3.9,py3.10,py3.11}-quart # Redis - {py2.7,py3.7,py3.8,py3.9}-redis + {py2.7,py3.7,py3.8,py3.9,py3.10,py3.11}-redis # Redis Cluster {py2.7,py3.7,py3.8,py3.9}-rediscluster-v{1,2.1.0,2} @@ -336,7 +336,7 @@ deps = # Redis redis: fakeredis!=1.7.4 - {py3.7,py3.8,py3.9}-redis: pytest-asyncio + {py3.7,py3.8,py3.9,py3.10,py3.11}-redis: pytest-asyncio # Redis Cluster rediscluster-v1: redis-py-cluster>=1.0.0,<2.0.0 From 4190091610834fcf1510636d8c1af5694bfa03a8 Mon Sep 17 00:00:00 2001 From: Zhenay Date: Fri, 17 Mar 2023 19:34:26 +0300 Subject: [PATCH 07/19] Run split-tox-gh-actions.py --- .github/workflows/test-integration-redis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-integration-redis.yml b/.github/workflows/test-integration-redis.yml index c612ca4ca3..a7c6cc64b4 100644 --- a/.github/workflows/test-integration-redis.yml +++ b/.github/workflows/test-integration-redis.yml @@ -31,7 +31,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["2.7","3.7","3.8","3.9"] + python-version: ["2.7","3.7","3.8","3.9","3.10","3.11"] # python3.6 reached EOL and is no longer being supported on # new versions of hosted runners on Github Actions # ubuntu-20.04 is the last version that supported python3.6 From 9621eff96123aa4b20102d35be0dc382c67a9fef Mon Sep 17 00:00:00 2001 From: Zhenay Date: Sat, 6 May 2023 15:04:48 +0300 Subject: [PATCH 08/19] Fix lint error --- sentry_sdk/integrations/redis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/redis/__init__.py b/sentry_sdk/integrations/redis/__init__.py index 35f6476d78..02d566e26a 100644 --- a/sentry_sdk/integrations/redis/__init__.py +++ b/sentry_sdk/integrations/redis/__init__.py @@ -105,7 +105,7 @@ def _patch_redis(redis): else: patch_redis_pipeline(strict_pipeline, False, _get_redis_command_args) try: - import redis.asyncio # type: ignore + import redis.asyncio except ImportError: pass else: From 3a45e86d906b727112dd8528b84294d31ba70de7 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 10 Jul 2023 15:09:03 +0200 Subject: [PATCH 09/19] Fixed merge conflict --- .github/workflows/test-integration-redis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test-integration-redis.yml b/.github/workflows/test-integration-redis.yml index 6897ae7865..e553e38085 100644 --- a/.github/workflows/test-integration-redis.yml +++ b/.github/workflows/test-integration-redis.yml @@ -31,11 +31,7 @@ jobs: strategy: fail-fast: false matrix: -<<<<<<< HEAD python-version: ["2.7","3.7","3.8","3.9","3.10","3.11"] -======= - python-version: ["3.7","3.8","3.9"] ->>>>>>> master # python3.6 reached EOL and is no longer being supported on # new versions of hosted runners on Github Actions # ubuntu-20.04 is the last version that supported python3.6 From 52ad8798b801640e50340dceb576a3adb2af8307 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 10 Jul 2023 15:41:46 +0200 Subject: [PATCH 10/19] Updated test matrix --- .github/workflows/test-integration-redis.yml | 2 +- sentry_sdk/integrations/redis/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-integration-redis.yml b/.github/workflows/test-integration-redis.yml index e553e38085..3a29033dcd 100644 --- a/.github/workflows/test-integration-redis.yml +++ b/.github/workflows/test-integration-redis.yml @@ -31,7 +31,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["2.7","3.7","3.8","3.9","3.10","3.11"] + python-version: ["3.7","3.8","3.9","3.10","3.11"] # python3.6 reached EOL and is no longer being supported on # new versions of hosted runners on Github Actions # ubuntu-20.04 is the last version that supported python3.6 diff --git a/sentry_sdk/integrations/redis/__init__.py b/sentry_sdk/integrations/redis/__init__.py index 91fd057129..39b1ef7edf 100644 --- a/sentry_sdk/integrations/redis/__init__.py +++ b/sentry_sdk/integrations/redis/__init__.py @@ -53,6 +53,7 @@ def _set_pipeline_data( "redis.commands", {"count": len(command_stack), "first_ten": commands}, ) + span.set_data(SPANDATA.DB_SYSTEM, "redis") def patch_redis_pipeline(pipeline_cls, is_cluster, get_command_args_fn): From 113a2e4d9faacee33cff8299e982a66202efe922 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 10 Jul 2023 15:43:16 +0200 Subject: [PATCH 11/19] linting --- sentry_sdk/integrations/redis/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/redis/__init__.py b/sentry_sdk/integrations/redis/__init__.py index 39b1ef7edf..18937c51ae 100644 --- a/sentry_sdk/integrations/redis/__init__.py +++ b/sentry_sdk/integrations/redis/__init__.py @@ -53,7 +53,6 @@ def _set_pipeline_data( "redis.commands", {"count": len(command_stack), "first_ten": commands}, ) - span.set_data(SPANDATA.DB_SYSTEM, "redis") def patch_redis_pipeline(pipeline_cls, is_cluster, get_command_args_fn): @@ -95,7 +94,7 @@ def _parse_rediscluster_command(command): return command.args -def _patch_redis(StrictRedis, client): +def _patch_redis(StrictRedis, client): # noqa: N803 # type: (Any, Any) -> None patch_redis_client(StrictRedis, is_cluster=False) patch_redis_pipeline(client.Pipeline, False, _get_redis_command_args) From e4395875ee5bc6071c4080ffaf87140eb4fe7546 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 10 Jul 2023 15:48:59 +0200 Subject: [PATCH 12/19] Formatting --- tests/integrations/redis/asyncio/test_redis.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integrations/redis/asyncio/test_redis.py b/tests/integrations/redis/asyncio/test_redis.py index fac01c6146..c3371a3fae 100644 --- a/tests/integrations/redis/asyncio/test_redis.py +++ b/tests/integrations/redis/asyncio/test_redis.py @@ -40,7 +40,6 @@ async def test_async_redis_pipeline(sentry_init, capture_events, is_transaction) connection = FakeRedis() with start_transaction(): - pipeline = connection.pipeline(transaction=is_transaction) pipeline.get("foo") pipeline.set("bar", 1) From c262b3bd2b751b89111434bd0b0b62e3862a84aa Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 10 Jul 2023 17:20:47 +0200 Subject: [PATCH 13/19] Added PII scrubbing --- sentry_sdk/integrations/redis/__init__.py | 67 +++++++++++++---------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/sentry_sdk/integrations/redis/__init__.py b/sentry_sdk/integrations/redis/__init__.py index 18937c51ae..233ce08649 100644 --- a/sentry_sdk/integrations/redis/__init__.py +++ b/sentry_sdk/integrations/redis/__init__.py @@ -26,10 +26,38 @@ ] _MAX_NUM_ARGS = 10 # Trim argument lists to this many values +_MAX_NUM_COMMANDS = 10 # Trim command lists to this many values _DEFAULT_MAX_DATA_SIZE = 1024 +def _get_safe_command(name, args): + command_parts = [name] + + for i, arg in enumerate(args): + if i > _MAX_NUM_ARGS: + break + + name_low = name.lower() + + if name_low in _COMMANDS_INCLUDING_SENSITIVE_DATA: + command_parts.append(SENSITIVE_DATA_SUBSTITUTE) + continue + + arg_is_the_key = i == 0 + if arg_is_the_key: + command_parts.append(repr(arg)) + + else: + if _should_send_default_pii(): + command_parts.append(repr(arg)) + else: + command_parts.append(SENSITIVE_DATA_SUBSTITUTE) + + command = " ".join(command_parts) + return command + + def _set_pipeline_data( span, is_cluster, get_command_args_fn, is_transaction, command_stack ): @@ -40,18 +68,18 @@ def _set_pipeline_data( commands = [] for i, arg in enumerate(command_stack): - if i > _MAX_NUM_ARGS: + if i >= _MAX_NUM_COMMANDS: break - command_args = [] - for j, command_arg in enumerate(get_command_args_fn(arg)): - if j > 0: - command_arg = repr(command_arg) - command_args.append(command_arg) - commands.append(" ".join(command_args)) + + command = get_command_args_fn(arg) + commands.append(_get_safe_command(command[0], command[1:])) span.set_data( "redis.commands", - {"count": len(command_stack), "first_ten": commands}, + { + "count": len(command_stack), + "first_ten": commands, + }, ) @@ -184,28 +212,7 @@ def _get_span_description(name, *args): description = name with capture_internal_exceptions(): - description_parts = [name] - for i, arg in enumerate(args): - if i > _MAX_NUM_ARGS: - break - - name_low = name.lower() - - if name_low in _COMMANDS_INCLUDING_SENSITIVE_DATA: - description_parts.append(SENSITIVE_DATA_SUBSTITUTE) - continue - - arg_is_the_key = i == 0 - if arg_is_the_key: - description_parts.append(repr(arg)) - - else: - if _should_send_default_pii(): - description_parts.append(repr(arg)) - else: - description_parts.append(SENSITIVE_DATA_SUBSTITUTE) - - description = " ".join(description_parts) + description = _get_safe_command(name, args) return description From e30e9218ab0e81faf6d5891b3810236cf6bba5cc Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 11 Jul 2023 09:09:13 +0200 Subject: [PATCH 14/19] Fixed tests --- tests/integrations/redis/asyncio/test_redis.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integrations/redis/asyncio/test_redis.py b/tests/integrations/redis/asyncio/test_redis.py index c3371a3fae..c533c5d6ac 100644 --- a/tests/integrations/redis/asyncio/test_redis.py +++ b/tests/integrations/redis/asyncio/test_redis.py @@ -23,6 +23,7 @@ async def test_async_basic(sentry_init, capture_events): "category": "redis", "message": "GET 'foobar'", "data": { + "db.operation": "GET", "redis.key": "foobar", "redis.command": "GET", "redis.is_cluster": False, @@ -53,7 +54,11 @@ async def test_async_redis_pipeline(sentry_init, capture_events, is_transaction) assert span["data"] == { "redis.commands": { "count": 3, - "first_ten": ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"], + "first_ten": [ + "GET 'foo'", + "SET 'bar' [Filtered]", + "SET 'baz' [Filtered]", + ], } } assert span["tags"] == { From feca3e7fd334617d5d6a2fa39bfd4b41fb797bdd Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 11 Jul 2023 09:27:05 +0200 Subject: [PATCH 15/19] Fixed tests --- scripts/runtox.sh | 2 +- .../redis/asyncio/{test_redis.py => test_redis_asyncio.py} | 0 tests/integrations/redis/test_redis.py | 6 +++++- 3 files changed, 6 insertions(+), 2 deletions(-) rename tests/integrations/redis/asyncio/{test_redis.py => test_redis_asyncio.py} (100%) diff --git a/scripts/runtox.sh b/scripts/runtox.sh index e099f44efe..31be9bfb4b 100755 --- a/scripts/runtox.sh +++ b/scripts/runtox.sh @@ -23,5 +23,5 @@ ENV="$($TOXPATH -l | grep "$searchstring" | tr $'\n' ',')" if [ "$ENV" = py2.7-common, ] || [ "$ENV" = py2.7-gevent, ]; then exec $TOXPATH -vv -e "$ENV" -- "${@:2}" else - exec $TOXPATH -vv -p auto -e "$ENV" -- "${@:2}" + exec $TOXPATH -vv -e "$ENV" -- "${@:2}" fi diff --git a/tests/integrations/redis/asyncio/test_redis.py b/tests/integrations/redis/asyncio/test_redis_asyncio.py similarity index 100% rename from tests/integrations/redis/asyncio/test_redis.py rename to tests/integrations/redis/asyncio/test_redis_asyncio.py diff --git a/tests/integrations/redis/test_redis.py b/tests/integrations/redis/test_redis.py index 23e0b43864..d6130f4d80 100644 --- a/tests/integrations/redis/test_redis.py +++ b/tests/integrations/redis/test_redis.py @@ -58,7 +58,11 @@ def test_redis_pipeline(sentry_init, capture_events, is_transaction): assert span["data"] == { "redis.commands": { "count": 3, - "first_ten": ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"], + "first_ten": [ + "GET 'foo'", + "SET 'bar' [Filtered]", + "SET 'baz' [Filtered]", + ], }, SPANDATA.DB_SYSTEM: "redis", } From d1bad283951aee4873c3d48c11aedd186c8e9c80 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 11 Jul 2023 09:32:05 +0200 Subject: [PATCH 16/19] Updated tests --- .../redis/asyncio/test_redis_asyncio.py | 24 ++++++++++++------- tests/integrations/redis/test_redis.py | 24 ++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/tests/integrations/redis/asyncio/test_redis_asyncio.py b/tests/integrations/redis/asyncio/test_redis_asyncio.py index c533c5d6ac..f97960f0eb 100644 --- a/tests/integrations/redis/asyncio/test_redis_asyncio.py +++ b/tests/integrations/redis/asyncio/test_redis_asyncio.py @@ -33,10 +33,22 @@ async def test_async_basic(sentry_init, capture_events): } -@pytest.mark.parametrize("is_transaction", [False, True]) +@pytest.mark.parametrize( + "is_transaction, send_default_pii, expected_first_ten", + [ + (False, False, ["GET 'foo'", "SET 'bar' [Filtered]", "SET 'baz' [Filtered]"]), + (True, True, ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"]), + ], +) @pytest.mark.asyncio -async def test_async_redis_pipeline(sentry_init, capture_events, is_transaction): - sentry_init(integrations=[RedisIntegration()], traces_sample_rate=1.0) +async def test_async_redis_pipeline( + sentry_init, capture_events, is_transaction, send_default_pii, expected_first_ten +): + sentry_init( + integrations=[RedisIntegration()], + traces_sample_rate=1.0, + send_default_pii=send_default_pii, + ) events = capture_events() connection = FakeRedis() @@ -54,11 +66,7 @@ async def test_async_redis_pipeline(sentry_init, capture_events, is_transaction) assert span["data"] == { "redis.commands": { "count": 3, - "first_ten": [ - "GET 'foo'", - "SET 'bar' [Filtered]", - "SET 'baz' [Filtered]", - ], + "first_ten": expected_first_ten, } } assert span["tags"] == { diff --git a/tests/integrations/redis/test_redis.py b/tests/integrations/redis/test_redis.py index d6130f4d80..e5d760b018 100644 --- a/tests/integrations/redis/test_redis.py +++ b/tests/integrations/redis/test_redis.py @@ -38,9 +38,21 @@ def test_basic(sentry_init, capture_events): } -@pytest.mark.parametrize("is_transaction", [False, True]) -def test_redis_pipeline(sentry_init, capture_events, is_transaction): - sentry_init(integrations=[RedisIntegration()], traces_sample_rate=1.0) +@pytest.mark.parametrize( + "is_transaction, send_default_pii, expected_first_ten", + [ + (False, False, ["GET 'foo'", "SET 'bar' [Filtered]", "SET 'baz' [Filtered]"]), + (True, True, ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"]), + ], +) +def test_redis_pipeline( + sentry_init, capture_events, is_transaction, send_default_pii, expected_first_ten +): + sentry_init( + integrations=[RedisIntegration()], + traces_sample_rate=1.0, + send_default_pii=send_default_pii, + ) events = capture_events() connection = FakeStrictRedis() @@ -58,11 +70,7 @@ def test_redis_pipeline(sentry_init, capture_events, is_transaction): assert span["data"] == { "redis.commands": { "count": 3, - "first_ten": [ - "GET 'foo'", - "SET 'bar' [Filtered]", - "SET 'baz' [Filtered]", - ], + "first_ten": expected_first_ten, }, SPANDATA.DB_SYSTEM: "redis", } From 9776c2657a073df51cbc292258db6c28d671713b Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 11 Jul 2023 09:50:57 +0200 Subject: [PATCH 17/19] More test updates --- .../rediscluster/test_rediscluster.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/integrations/rediscluster/test_rediscluster.py b/tests/integrations/rediscluster/test_rediscluster.py index c4b5a8e7d3..32eb8c4fa5 100644 --- a/tests/integrations/rediscluster/test_rediscluster.py +++ b/tests/integrations/rediscluster/test_rediscluster.py @@ -52,8 +52,21 @@ def test_rediscluster_basic(rediscluster_cls, sentry_init, capture_events): } -def test_rediscluster_pipeline(sentry_init, capture_events): - sentry_init(integrations=[RedisIntegration()], traces_sample_rate=1.0) +@pytest.mark.parametrize( + "send_default_pii, expected_first_ten", + [ + (False, ["GET 'foo'", "SET 'bar' [Filtered]", "SET 'baz' [Filtered]"]), + (True, ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"]), + ], +) +def test_rediscluster_pipeline( + sentry_init, capture_events, send_default_pii, expected_first_ten +): + sentry_init( + integrations=[RedisIntegration()], + traces_sample_rate=1.0, + send_default_pii=send_default_pii, + ) events = capture_events() rc = rediscluster.RedisCluster(connection_pool=True) @@ -71,7 +84,7 @@ def test_rediscluster_pipeline(sentry_init, capture_events): assert span["data"] == { "redis.commands": { "count": 3, - "first_ten": ["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"], + "first_ten": expected_first_ten, }, SPANDATA.DB_SYSTEM: "redis", } From e8b240c82de0b5a9c9877efa81cc2cc62637ae2c Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 11 Jul 2023 10:00:50 +0200 Subject: [PATCH 18/19] Typing --- sentry_sdk/integrations/redis/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry_sdk/integrations/redis/__init__.py b/sentry_sdk/integrations/redis/__init__.py index 233ce08649..b0a4a8d1ed 100644 --- a/sentry_sdk/integrations/redis/__init__.py +++ b/sentry_sdk/integrations/redis/__init__.py @@ -32,6 +32,7 @@ def _get_safe_command(name, args): + # type: (str, Sequence[Any]) -> str command_parts = [name] for i, arg in enumerate(args): From 0d06afddd8a8d710f3e41b570902577f1762f011 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 11 Jul 2023 10:18:48 +0200 Subject: [PATCH 19/19] Revert accidental commit --- scripts/runtox.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/runtox.sh b/scripts/runtox.sh index 31be9bfb4b..e099f44efe 100755 --- a/scripts/runtox.sh +++ b/scripts/runtox.sh @@ -23,5 +23,5 @@ ENV="$($TOXPATH -l | grep "$searchstring" | tr $'\n' ',')" if [ "$ENV" = py2.7-common, ] || [ "$ENV" = py2.7-gevent, ]; then exec $TOXPATH -vv -e "$ENV" -- "${@:2}" else - exec $TOXPATH -vv -e "$ENV" -- "${@:2}" + exec $TOXPATH -vv -p auto -e "$ENV" -- "${@:2}" fi