diff --git a/sentry_sdk/integrations/redis/_async_common.py b/sentry_sdk/integrations/redis/_async_common.py index 1afc355843..b033b6c86e 100644 --- a/sentry_sdk/integrations/redis/_async_common.py +++ b/sentry_sdk/integrations/redis/_async_common.py @@ -11,13 +11,15 @@ _set_pipeline_data, ) from sentry_sdk.tracing import Span +from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import capture_internal_exceptions from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Callable - from typing import Any, Union + from typing import Any, Optional, Union + from sentry_sdk.traces import StreamedSpan from redis.asyncio.client import Pipeline, StrictRedis from redis.asyncio.cluster import ClusterPipeline, RedisCluster @@ -26,21 +28,36 @@ def patch_redis_async_pipeline( pipeline_cls: "Union[type[Pipeline[Any]], type[ClusterPipeline[Any]]]", is_cluster: bool, get_command_args_fn: "Any", - set_db_data_fn: "Callable[[Span, Any], None]", + set_db_data_fn: "Callable[[Union[Span, StreamedSpan], Any], None]", ) -> None: old_execute = pipeline_cls.execute from sentry_sdk.integrations.redis import RedisIntegration async def _sentry_execute(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": - if sentry_sdk.get_client().get_integration(RedisIntegration) is None: + client = sentry_sdk.get_client() + if client.get_integration(RedisIntegration) is None: return await old_execute(self, *args, **kwargs) - with sentry_sdk.start_span( - op=OP.DB_REDIS, - name="redis.pipeline.execute", - origin=SPAN_ORIGIN, - ) as span: + span_streaming = has_span_streaming_enabled(client.options) + + span: "Union[Span, StreamedSpan]" + if span_streaming: + span = sentry_sdk.traces.start_span( + name="redis.pipeline.execute", + attributes={ + "sentry.origin": SPAN_ORIGIN, + "sentry.op": OP.DB_REDIS, + }, + ) + else: + span = sentry_sdk.start_span( + op=OP.DB_REDIS, + name="redis.pipeline.execute", + origin=SPAN_ORIGIN, + ) + + with span: with capture_internal_exceptions(): try: command_seq = self._execution_strategy._command_queue @@ -67,7 +84,7 @@ async def _sentry_execute(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": def patch_redis_async_client( cls: "Union[type[StrictRedis[Any]], type[RedisCluster[Any]]]", is_cluster: bool, - set_db_data_fn: "Callable[[Span, Any], None]", + set_db_data_fn: "Callable[[Union[Span, StreamedSpan], Any], None]", ) -> None: old_execute_command = cls.execute_command @@ -76,10 +93,13 @@ def patch_redis_async_client( async def _sentry_execute_command( self: "Any", name: str, *args: "Any", **kwargs: "Any" ) -> "Any": - integration = sentry_sdk.get_client().get_integration(RedisIntegration) + client = sentry_sdk.get_client() + integration = client.get_integration(RedisIntegration) if integration is None: return await old_execute_command(self, name, *args, **kwargs) + span_streaming = has_span_streaming_enabled(client.options) + cache_properties = _compile_cache_span_properties( name, args, @@ -87,22 +107,41 @@ async def _sentry_execute_command( integration, ) - cache_span = None + cache_span: "Optional[Union[Span, StreamedSpan]]" = None if cache_properties["is_cache_key"] and cache_properties["op"] is not None: - cache_span = sentry_sdk.start_span( - op=cache_properties["op"], - name=cache_properties["description"], - origin=SPAN_ORIGIN, - ) + if span_streaming: + cache_span = sentry_sdk.traces.start_span( + name=cache_properties["description"], + attributes={ + "sentry.op": cache_properties["op"], + "sentry.origin": SPAN_ORIGIN, + }, + ) + else: + cache_span = sentry_sdk.start_span( + op=cache_properties["op"], + name=cache_properties["description"], + origin=SPAN_ORIGIN, + ) cache_span.__enter__() db_properties = _compile_db_span_properties(integration, name, args) - db_span = sentry_sdk.start_span( - op=db_properties["op"], - name=db_properties["description"], - origin=SPAN_ORIGIN, - ) + db_span: "Union[Span, StreamedSpan]" + if span_streaming: + db_span = sentry_sdk.traces.start_span( + name=db_properties["description"], + attributes={ + "sentry.op": db_properties["op"], + "sentry.origin": SPAN_ORIGIN, + }, + ) + else: + db_span = sentry_sdk.start_span( + op=db_properties["op"], + name=db_properties["description"], + origin=SPAN_ORIGIN, + ) db_span.__enter__() set_db_data_fn(db_span, self) diff --git a/sentry_sdk/integrations/redis/_sync_common.py b/sentry_sdk/integrations/redis/_sync_common.py index 4624260f6a..8799ae1d5f 100644 --- a/sentry_sdk/integrations/redis/_sync_common.py +++ b/sentry_sdk/integrations/redis/_sync_common.py @@ -11,34 +11,51 @@ _set_pipeline_data, ) from sentry_sdk.tracing import Span +from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import capture_internal_exceptions from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Callable - from typing import Any + from typing import Any, Optional, Union + from sentry_sdk.traces import StreamedSpan def patch_redis_pipeline( pipeline_cls: "Any", is_cluster: bool, get_command_args_fn: "Any", - set_db_data_fn: "Callable[[Span, Any], None]", + set_db_data_fn: "Callable[[Union[Span, StreamedSpan], Any], None]", ) -> None: old_execute = pipeline_cls.execute from sentry_sdk.integrations.redis import RedisIntegration def sentry_patched_execute(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": - if sentry_sdk.get_client().get_integration(RedisIntegration) is None: + client = sentry_sdk.get_client() + if client.get_integration(RedisIntegration) is None: return old_execute(self, *args, **kwargs) - with sentry_sdk.start_span( - op=OP.DB_REDIS, - name="redis.pipeline.execute", - origin=SPAN_ORIGIN, - ) as span: + span_streaming = has_span_streaming_enabled(client.options) + + span: "Union[Span, StreamedSpan]" + if span_streaming: + span = sentry_sdk.traces.start_span( + name="redis.pipeline.execute", + attributes={ + "sentry.origin": SPAN_ORIGIN, + "sentry.op": OP.DB_REDIS, + }, + ) + else: + span = sentry_sdk.start_span( + op=OP.DB_REDIS, + name="redis.pipeline.execute", + origin=SPAN_ORIGIN, + ) + + with span: with capture_internal_exceptions(): command_seq = None try: @@ -61,7 +78,9 @@ def sentry_patched_execute(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": def patch_redis_client( - cls: "Any", is_cluster: bool, set_db_data_fn: "Callable[[Span, Any], None]" + cls: "Any", + is_cluster: bool, + set_db_data_fn: "Callable[[Union[Span, StreamedSpan], Any], None]", ) -> None: """ This function can be used to instrument custom redis client classes or @@ -74,10 +93,13 @@ def patch_redis_client( def sentry_patched_execute_command( self: "Any", name: str, *args: "Any", **kwargs: "Any" ) -> "Any": - integration = sentry_sdk.get_client().get_integration(RedisIntegration) + client = sentry_sdk.get_client() + integration = client.get_integration(RedisIntegration) if integration is None: return old_execute_command(self, name, *args, **kwargs) + span_streaming = has_span_streaming_enabled(client.options) + cache_properties = _compile_cache_span_properties( name, args, @@ -85,22 +107,41 @@ def sentry_patched_execute_command( integration, ) - cache_span = None + cache_span: "Optional[Union[Span, StreamedSpan]]" = None if cache_properties["is_cache_key"] and cache_properties["op"] is not None: - cache_span = sentry_sdk.start_span( - op=cache_properties["op"], - name=cache_properties["description"], - origin=SPAN_ORIGIN, - ) + if span_streaming: + cache_span = sentry_sdk.traces.start_span( + name=cache_properties["description"], + attributes={ + "sentry.op": cache_properties["op"], + "sentry.origin": SPAN_ORIGIN, + }, + ) + else: + cache_span = sentry_sdk.start_span( + op=cache_properties["op"], + name=cache_properties["description"], + origin=SPAN_ORIGIN, + ) cache_span.__enter__() db_properties = _compile_db_span_properties(integration, name, args) - db_span = sentry_sdk.start_span( - op=db_properties["op"], - name=db_properties["description"], - origin=SPAN_ORIGIN, - ) + db_span: "Union[Span, StreamedSpan]" + if span_streaming: + db_span = sentry_sdk.traces.start_span( + name=db_properties["description"], + attributes={ + "sentry.op": db_properties["op"], + "sentry.origin": SPAN_ORIGIN, + }, + ) + else: + db_span = sentry_sdk.start_span( + op=db_properties["op"], + name=db_properties["description"], + origin=SPAN_ORIGIN, + ) db_span.__enter__() set_db_data_fn(db_span, self) diff --git a/sentry_sdk/integrations/redis/modules/caches.py b/sentry_sdk/integrations/redis/modules/caches.py index ee5a7d3943..e4c059bb2e 100644 --- a/sentry_sdk/integrations/redis/modules/caches.py +++ b/sentry_sdk/integrations/redis/modules/caches.py @@ -4,6 +4,7 @@ from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations.redis.utils import _get_safe_key, _key_as_string +from sentry_sdk.traces import StreamedSpan from sentry_sdk.utils import capture_internal_exceptions GET_COMMANDS = ("get", "mget") @@ -14,7 +15,7 @@ if TYPE_CHECKING: from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.tracing import Span - from typing import Any, Optional + from typing import Any, Optional, Union def _get_op(name: str) -> "Optional[str]": @@ -80,25 +81,30 @@ def _get_cache_span_description( def _set_cache_data( - span: "Span", + span: "Union[Span, StreamedSpan]", redis_client: "Any", properties: "dict[str, Any]", return_value: "Optional[Any]", ) -> None: + if isinstance(span, StreamedSpan): + set_on_span = span.set_attribute + else: + set_on_span = span.set_data + with capture_internal_exceptions(): - span.set_data(SPANDATA.CACHE_KEY, properties["key"]) + set_on_span(SPANDATA.CACHE_KEY, properties["key"]) if properties["redis_command"] in GET_COMMANDS: if return_value is not None: - span.set_data(SPANDATA.CACHE_HIT, True) + set_on_span(SPANDATA.CACHE_HIT, True) size = ( len(str(return_value).encode("utf-8")) if not isinstance(return_value, bytes) else len(return_value) ) - span.set_data(SPANDATA.CACHE_ITEM_SIZE, size) + set_on_span(SPANDATA.CACHE_ITEM_SIZE, size) else: - span.set_data(SPANDATA.CACHE_HIT, False) + set_on_span(SPANDATA.CACHE_HIT, False) elif properties["redis_command"] in SET_COMMANDS: if properties["value"] is not None: @@ -107,7 +113,7 @@ def _set_cache_data( if not isinstance(properties["value"], bytes) else len(properties["value"]) ) - span.set_data(SPANDATA.CACHE_ITEM_SIZE, size) + set_on_span(SPANDATA.CACHE_ITEM_SIZE, size) try: connection_params = redis_client.connection_pool.connection_kwargs @@ -122,8 +128,8 @@ def _set_cache_data( host = connection_params.get("host") if host is not None: - span.set_data(SPANDATA.NETWORK_PEER_ADDRESS, host) + set_on_span(SPANDATA.NETWORK_PEER_ADDRESS, host) port = connection_params.get("port") if port is not None: - span.set_data(SPANDATA.NETWORK_PEER_PORT, port) + set_on_span(SPANDATA.NETWORK_PEER_PORT, port) diff --git a/sentry_sdk/integrations/redis/modules/queries.py b/sentry_sdk/integrations/redis/modules/queries.py index c7780099c3..a6bd8cff20 100644 --- a/sentry_sdk/integrations/redis/modules/queries.py +++ b/sentry_sdk/integrations/redis/modules/queries.py @@ -4,6 +4,7 @@ from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations.redis.utils import _get_safe_command +from sentry_sdk.traces import StreamedSpan from sentry_sdk.utils import capture_internal_exceptions from typing import TYPE_CHECKING @@ -12,7 +13,7 @@ from redis import Redis from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.tracing import Span - from typing import Any + from typing import Any, Union def _compile_db_span_properties( @@ -42,24 +43,43 @@ def _get_db_span_description( return description -def _set_db_data_on_span(span: "Span", connection_params: "dict[str, Any]") -> None: - span.set_data(SPANDATA.DB_SYSTEM, "redis") - span.set_data(SPANDATA.DB_DRIVER_NAME, "redis-py") - +def _set_db_data_on_span( + span: "Union[Span, StreamedSpan]", connection_params: "dict[str, Any]" +) -> None: db = connection_params.get("db") - if db is not None: - span.set_data(SPANDATA.DB_NAME, str(db)) - host = connection_params.get("host") - if host is not None: - span.set_data(SPANDATA.SERVER_ADDRESS, host) - port = connection_params.get("port") - if port is not None: - span.set_data(SPANDATA.SERVER_PORT, port) + + if isinstance(span, StreamedSpan): + span.set_attribute(SPANDATA.DB_SYSTEM_NAME, "redis") + span.set_attribute(SPANDATA.DB_DRIVER_NAME, "redis-py") + + if db is not None: + span.set_attribute(SPANDATA.DB_NAMESPACE, str(db)) + + if host is not None: + span.set_attribute(SPANDATA.SERVER_ADDRESS, host) + + if port is not None: + span.set_attribute(SPANDATA.SERVER_PORT, port) + + else: + span.set_data(SPANDATA.DB_SYSTEM, "redis") + span.set_data(SPANDATA.DB_DRIVER_NAME, "redis-py") + + if db is not None: + span.set_data(SPANDATA.DB_NAME, str(db)) + + if host is not None: + span.set_data(SPANDATA.SERVER_ADDRESS, host) + + if port is not None: + span.set_data(SPANDATA.SERVER_PORT, port) -def _set_db_data(span: "Span", redis_instance: "Redis[Any]") -> None: +def _set_db_data( + span: "Union[Span, StreamedSpan]", redis_instance: "Redis[Any]" +) -> None: try: _set_db_data_on_span(span, redis_instance.connection_pool.connection_kwargs) except AttributeError: diff --git a/sentry_sdk/integrations/redis/redis_cluster.py b/sentry_sdk/integrations/redis/redis_cluster.py index b73a8e730c..e25812c266 100644 --- a/sentry_sdk/integrations/redis/redis_cluster.py +++ b/sentry_sdk/integrations/redis/redis_cluster.py @@ -17,17 +17,19 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any + from typing import Any, Union from redis import RedisCluster from redis.asyncio.cluster import ( RedisCluster as AsyncRedisCluster, ClusterPipeline as AsyncClusterPipeline, ) + from sentry_sdk.traces import StreamedSpan from sentry_sdk.tracing import Span def _set_async_cluster_db_data( - span: "Span", async_redis_cluster_instance: "AsyncRedisCluster[Any]" + span: "Union[Span, StreamedSpan]", + async_redis_cluster_instance: "AsyncRedisCluster[Any]", ) -> None: default_node = async_redis_cluster_instance.get_default_node() if default_node is not None and default_node.connection_kwargs is not None: @@ -35,7 +37,8 @@ def _set_async_cluster_db_data( def _set_async_cluster_pipeline_db_data( - span: "Span", async_redis_cluster_pipeline_instance: "AsyncClusterPipeline[Any]" + span: "Union[Span, StreamedSpan]", + async_redis_cluster_pipeline_instance: "AsyncClusterPipeline[Any]", ) -> None: with capture_internal_exceptions(): client = getattr(async_redis_cluster_pipeline_instance, "cluster_client", None) @@ -55,7 +58,7 @@ def _set_async_cluster_pipeline_db_data( def _set_cluster_db_data( - span: "Span", redis_cluster_instance: "RedisCluster[Any]" + span: "Union[Span, StreamedSpan]", redis_cluster_instance: "RedisCluster[Any]" ) -> None: default_node = redis_cluster_instance.get_default_node() diff --git a/sentry_sdk/integrations/redis/utils.py b/sentry_sdk/integrations/redis/utils.py index 81d544a75a..898a2cd54b 100644 --- a/sentry_sdk/integrations/redis/utils.py +++ b/sentry_sdk/integrations/redis/utils.py @@ -7,13 +7,14 @@ _SINGLE_KEY_COMMANDS, ) from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.traces import StreamedSpan +from sentry_sdk.tracing import Span from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, Optional, Sequence - from sentry_sdk.tracing import Span + from typing import Any, Optional, Sequence, Union def _get_safe_command(name: str, args: "Sequence[Any]") -> str: @@ -105,12 +106,16 @@ def _parse_rediscluster_command(command: "Any") -> "Sequence[Any]": def _set_pipeline_data( - span: "Span", + span: "Union[Span, StreamedSpan]", is_cluster: bool, get_command_args_fn: "Any", is_transaction: bool, commands_seq: "Sequence[Any]", ) -> None: + # TODO: Remove this whole function when removing transaction based tracing + if isinstance(span, StreamedSpan): + return + span.set_tag("redis.is_cluster", is_cluster) span.set_tag("redis.transaction", is_transaction) @@ -131,15 +136,24 @@ def _set_pipeline_data( ) -def _set_client_data(span: "Span", is_cluster: bool, name: str, *args: "Any") -> None: - span.set_tag("redis.is_cluster", is_cluster) - if name: - span.set_tag("redis.command", name) - span.set_tag(SPANDATA.DB_OPERATION, name) +def _set_client_data( + span: "Union[Span, StreamedSpan]", is_cluster: bool, name: str, *args: "Any" +) -> None: + if isinstance(span, StreamedSpan): + if name: + span.set_attribute(SPANDATA.DB_OPERATION_NAME, name) + else: + span.set_tag("redis.is_cluster", is_cluster) + if name: + span.set_tag("redis.command", name) + span.set_tag(SPANDATA.DB_OPERATION, 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]) + if isinstance(span, StreamedSpan): + span.set_attribute("db.redis.key", args[0]) + else: + span.set_tag("redis.key", args[0]) diff --git a/tests/integrations/redis/asyncio/test_redis_asyncio.py b/tests/integrations/redis/asyncio/test_redis_asyncio.py index 17130b337b..fe24b35e22 100644 --- a/tests/integrations/redis/asyncio/test_redis_asyncio.py +++ b/tests/integrations/redis/asyncio/test_redis_asyncio.py @@ -1,5 +1,6 @@ import pytest +import sentry_sdk from sentry_sdk import capture_message, start_transaction from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.redis import RedisIntegration @@ -35,6 +36,7 @@ async def test_async_basic(sentry_init, capture_events): } +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "is_transaction, send_default_pii, expected_first_ten", [ @@ -44,69 +46,128 @@ async def test_async_basic(sentry_init, capture_events): ) @pytest.mark.asyncio async def test_async_redis_pipeline( - sentry_init, capture_events, is_transaction, send_default_pii, expected_first_ten + sentry_init, + capture_events, + capture_items, + is_transaction, + send_default_pii, + expected_first_ten, + span_streaming, ): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, send_default_pii=send_default_pii, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - 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"] == ApproxDict( - { - "redis.commands": { - "count": 3, - "first_ten": expected_first_ten, - }, - SPANDATA.DB_SYSTEM: "redis", - SPANDATA.DB_NAME: "0", - SPANDATA.SERVER_ADDRESS: connection.connection_pool.connection_kwargs.get( - "host" - ), - SPANDATA.SERVER_PORT: 6379, + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + pipeline = connection.pipeline(transaction=is_transaction) + pipeline.get("foo") + pipeline.set("bar", 1) + pipeline.set("baz", 2) + await pipeline.execute() + sentry_sdk.flush() + + assert len(items) == 2 + pipeline_span, parent_span = items[0].payload, items[1].payload + + assert parent_span["name"] == "custom parent" + assert pipeline_span["name"] == "redis.pipeline.execute" + attrs = pipeline_span["attributes"] + assert attrs["sentry.op"] == "db.redis" + assert attrs[SPANDATA.DB_SYSTEM_NAME] == "redis" + assert attrs[SPANDATA.DB_DRIVER_NAME] == "redis-py" + assert attrs[SPANDATA.DB_NAMESPACE] == "0" + assert attrs[SPANDATA.SERVER_ADDRESS] == ( + connection.connection_pool.connection_kwargs.get("host") + ) + assert attrs[SPANDATA.SERVER_PORT] == 6379 + else: + events = capture_events() + 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"] == ApproxDict( + { + "redis.commands": { + "count": 3, + "first_ten": expected_first_ten, + }, + SPANDATA.DB_SYSTEM: "redis", + SPANDATA.DB_NAME: "0", + SPANDATA.SERVER_ADDRESS: connection.connection_pool.connection_kwargs.get( + "host" + ), + SPANDATA.SERVER_PORT: 6379, + } + ) + assert span["tags"] == { + "redis.transaction": is_transaction, + "redis.is_cluster": False, } - ) - assert span["tags"] == { - "redis.transaction": is_transaction, - "redis.is_cluster": False, - } +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio -async def test_async_span_origin(sentry_init, capture_events): +async def test_async_span_origin( + sentry_init, capture_events, capture_items, span_streaming +): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeRedis() - with start_transaction(name="custom_transaction"): - # default case - await connection.set("somekey", "somevalue") - - # pipeline - pipeline = connection.pipeline(transaction=False) - pipeline.get("somekey") - pipeline.set("anotherkey", 1) - await pipeline.execute() - - (event,) = events - - assert event["contexts"]["trace"]["origin"] == "manual" - for span in event["spans"]: - assert span["origin"] == "auto.db.redis" + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + # default case + await connection.set("somekey", "somevalue") + + # pipeline + pipeline = connection.pipeline(transaction=False) + pipeline.get("somekey") + pipeline.set("anotherkey", 1) + await pipeline.execute() + sentry_sdk.flush() + + assert len(items) == 3 + set_span, pipeline_span, parent_span = [item.payload for item in items] + + assert parent_span["name"] == "custom parent" + assert parent_span["attributes"]["sentry.origin"] == "manual" + assert set_span["attributes"]["sentry.origin"] == "auto.db.redis" + assert pipeline_span["attributes"]["sentry.origin"] == "auto.db.redis" + else: + events = capture_events() + with start_transaction(name="custom_transaction"): + # default case + await connection.set("somekey", "somevalue") + + # pipeline + pipeline = connection.pipeline(transaction=False) + pipeline.get("somekey") + pipeline.set("anotherkey", 1) + await pipeline.execute() + + (event,) = events + + assert event["contexts"]["trace"]["origin"] == "manual" + + for span in event["spans"]: + assert span["origin"] == "auto.db.redis" diff --git a/tests/integrations/redis/cluster/test_redis_cluster.py b/tests/integrations/redis/cluster/test_redis_cluster.py index 83d1b45cc9..a9940ff35d 100644 --- a/tests/integrations/redis/cluster/test_redis_cluster.py +++ b/tests/integrations/redis/cluster/test_redis_cluster.py @@ -1,4 +1,6 @@ import pytest + +import sentry_sdk from sentry_sdk import capture_message from sentry_sdk.consts import SPANDATA from sentry_sdk.api import start_transaction @@ -53,6 +55,7 @@ def test_rediscluster_breadcrumb(sentry_init, capture_events): } +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "send_default_pii, description", [ @@ -60,45 +63,81 @@ def test_rediscluster_breadcrumb(sentry_init, capture_events): (True, "SET 'bar' 1"), ], ) -def test_rediscluster_basic(sentry_init, capture_events, send_default_pii, description): +def test_rediscluster_basic( + sentry_init, + capture_events, + capture_items, + send_default_pii, + description, + span_streaming, +): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, send_default_pii=send_default_pii, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() - with start_transaction(): - rc = redis.RedisCluster(host="localhost", port=6379) - rc.set("bar", 1) + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + rc = redis.RedisCluster(host="localhost", port=6379) + rc.set("bar", 1) + sentry_sdk.flush() - (event,) = events - spans = event["spans"] + # on initializing a RedisCluster, a COMMAND call may be emitted + payloads = [item.payload for item in items] + parent_span = payloads[-1] + redis_spans = payloads[:-1] + assert parent_span["name"] == "custom parent" + assert len(redis_spans) in (1, 2) + assert len(redis_spans) == 1 or redis_spans[0]["name"] == "COMMAND" - # on initializing a RedisCluster, a COMMAND call is made - this is not important for the test - # but must be accounted for - assert len(spans) in (1, 2) - assert len(spans) == 1 or spans[0]["description"] == "COMMAND" - - span = spans[-1] - assert span["op"] == "db.redis" - assert span["description"] == description - assert span["data"] == ApproxDict( - { - SPANDATA.DB_SYSTEM: "redis", - # ClusterNode converts localhost to 127.0.0.1 - SPANDATA.SERVER_ADDRESS: "127.0.0.1", - SPANDATA.SERVER_PORT: 6379, + span = redis_spans[-1] + assert span["name"] == description + attrs = span["attributes"] + assert attrs["sentry.op"] == "db.redis" + assert attrs[SPANDATA.DB_SYSTEM_NAME] == "redis" + assert attrs[SPANDATA.DB_DRIVER_NAME] == "redis-py" + # ClusterNode converts localhost to 127.0.0.1 + assert attrs[SPANDATA.SERVER_ADDRESS] == "127.0.0.1" + assert attrs[SPANDATA.SERVER_PORT] == 6379 + assert attrs[SPANDATA.DB_OPERATION_NAME] == "SET" + assert attrs["db.redis.key"] == "bar" + else: + events = capture_events() + with start_transaction(): + rc = redis.RedisCluster(host="localhost", port=6379) + rc.set("bar", 1) + + (event,) = events + spans = event["spans"] + + # on initializing a RedisCluster, a COMMAND call is made - this is not important for the test + # but must be accounted for + assert len(spans) in (1, 2) + assert len(spans) == 1 or spans[0]["description"] == "COMMAND" + + span = spans[-1] + assert span["op"] == "db.redis" + assert span["description"] == description + assert span["data"] == ApproxDict( + { + SPANDATA.DB_SYSTEM: "redis", + # ClusterNode converts localhost to 127.0.0.1 + SPANDATA.SERVER_ADDRESS: "127.0.0.1", + SPANDATA.SERVER_PORT: 6379, + } + ) + assert span["tags"] == { + "db.operation": "SET", + "redis.command": "SET", + "redis.is_cluster": True, + "redis.key": "bar", } - ) - assert span["tags"] == { - "db.operation": "SET", - "redis.command": "SET", - "redis.is_cluster": True, - "redis.key": "bar", - } +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "send_default_pii, expected_first_ten", [ @@ -107,66 +146,129 @@ def test_rediscluster_basic(sentry_init, capture_events, send_default_pii, descr ], ) def test_rediscluster_pipeline( - sentry_init, capture_events, send_default_pii, expected_first_ten + sentry_init, + capture_events, + capture_items, + send_default_pii, + expected_first_ten, + span_streaming, ): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, send_default_pii=send_default_pii, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() rc = redis.RedisCluster(host="localhost", port=6379) - with start_transaction(): - pipeline = rc.pipeline() - 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"] == ApproxDict( - { - "redis.commands": { - "count": 3, - "first_ten": expected_first_ten, - }, - SPANDATA.DB_SYSTEM: "redis", - # ClusterNode converts localhost to 127.0.0.1 - SPANDATA.SERVER_ADDRESS: "127.0.0.1", - SPANDATA.SERVER_PORT: 6379, + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + pipeline = rc.pipeline() + pipeline.get("foo") + pipeline.set("bar", 1) + pipeline.set("baz", 2) + pipeline.execute() + sentry_sdk.flush() + + # on initializing a RedisCluster, a COMMAND call may be emitted + payloads = [item.payload for item in items] + parent_span = payloads[-1] + redis_spans = payloads[:-1] + assert parent_span["name"] == "custom parent" + assert len(redis_spans) in (1, 2) + assert len(redis_spans) == 1 or redis_spans[0]["name"] == "COMMAND" + + pipeline_span = redis_spans[-1] + assert pipeline_span["name"] == "redis.pipeline.execute" + attrs = pipeline_span["attributes"] + assert attrs["sentry.op"] == "db.redis" + assert attrs[SPANDATA.DB_SYSTEM_NAME] == "redis" + assert attrs[SPANDATA.DB_DRIVER_NAME] == "redis-py" + # ClusterNode converts localhost to 127.0.0.1 + assert attrs[SPANDATA.SERVER_ADDRESS] == "127.0.0.1" + assert attrs[SPANDATA.SERVER_PORT] == 6379 + else: + events = capture_events() + with start_transaction(): + pipeline = rc.pipeline() + 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"] == ApproxDict( + { + "redis.commands": { + "count": 3, + "first_ten": expected_first_ten, + }, + SPANDATA.DB_SYSTEM: "redis", + # ClusterNode converts localhost to 127.0.0.1 + SPANDATA.SERVER_ADDRESS: "127.0.0.1", + SPANDATA.SERVER_PORT: 6379, + } + ) + assert span["tags"] == { + "redis.transaction": False, # For Cluster, this is always False + "redis.is_cluster": True, } - ) - assert span["tags"] == { - "redis.transaction": False, # For Cluster, this is always False - "redis.is_cluster": True, - } -def test_rediscluster_span_origin(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_rediscluster_span_origin( + sentry_init, capture_events, capture_items, span_streaming +): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() rc = redis.RedisCluster(host="localhost", port=6379) - with start_transaction(name="custom_transaction"): - # default case - rc.set("somekey", "somevalue") - # pipeline - pipeline = rc.pipeline(transaction=False) - pipeline.get("somekey") - pipeline.set("anotherkey", 1) - pipeline.execute() + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + # default case + rc.set("somekey", "somevalue") - (event,) = events + # pipeline + pipeline = rc.pipeline(transaction=False) + pipeline.get("somekey") + pipeline.set("anotherkey", 1) + pipeline.execute() + sentry_sdk.flush() + + payloads = [item.payload for item in items] + parent_span = payloads[-1] + redis_spans = payloads[:-1] + + assert parent_span["name"] == "custom parent" + assert parent_span["attributes"]["sentry.origin"] == "manual" + assert len(redis_spans) >= 2 + for span in redis_spans: + assert span["attributes"]["sentry.origin"] == "auto.db.redis" + else: + events = capture_events() + with start_transaction(name="custom_transaction"): + # default case + rc.set("somekey", "somevalue") + + # pipeline + pipeline = rc.pipeline(transaction=False) + pipeline.get("somekey") + pipeline.set("anotherkey", 1) + pipeline.execute() + + (event,) = events - assert event["contexts"]["trace"]["origin"] == "manual" + assert event["contexts"]["trace"]["origin"] == "manual" - for span in event["spans"]: - assert span["origin"] == "auto.db.redis" + for span in event["spans"]: + assert span["origin"] == "auto.db.redis" diff --git a/tests/integrations/redis/cluster_asyncio/test_redis_cluster_asyncio.py b/tests/integrations/redis/cluster_asyncio/test_redis_cluster_asyncio.py index 993a2962ca..d4e7e0355e 100644 --- a/tests/integrations/redis/cluster_asyncio/test_redis_cluster_asyncio.py +++ b/tests/integrations/redis/cluster_asyncio/test_redis_cluster_asyncio.py @@ -1,5 +1,6 @@ import pytest +import sentry_sdk from sentry_sdk import capture_message, start_transaction from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.redis import RedisIntegration @@ -61,6 +62,7 @@ async def test_async_breadcrumb(sentry_init, capture_events): } +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "send_default_pii, description", [ @@ -69,38 +71,68 @@ async def test_async_breadcrumb(sentry_init, capture_events): ], ) @pytest.mark.asyncio -async def test_async_basic(sentry_init, capture_events, send_default_pii, description): +async def test_async_basic( + sentry_init, + capture_events, + capture_items, + send_default_pii, + description, + span_streaming, +): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, send_default_pii=send_default_pii, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = cluster.RedisCluster(host="localhost", port=6379) - with start_transaction(): - await connection.set("bar", 1) - (event,) = events - (span,) = event["spans"] - assert span["op"] == "db.redis" - assert span["description"] == description - assert span["data"] == ApproxDict( - { - SPANDATA.DB_SYSTEM: "redis", - # ClusterNode converts localhost to 127.0.0.1 - SPANDATA.SERVER_ADDRESS: "127.0.0.1", - SPANDATA.SERVER_PORT: 6379, + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + await connection.set("bar", 1) + sentry_sdk.flush() + + assert len(items) == 2 + redis_span, parent_span = items[0].payload, items[1].payload + + assert parent_span["name"] == "custom parent" + assert redis_span["name"] == description + attrs = redis_span["attributes"] + assert attrs["sentry.op"] == "db.redis" + assert attrs[SPANDATA.DB_SYSTEM_NAME] == "redis" + assert attrs[SPANDATA.DB_DRIVER_NAME] == "redis-py" + assert attrs[SPANDATA.SERVER_ADDRESS] == "127.0.0.1" + assert attrs[SPANDATA.SERVER_PORT] == 6379 + assert attrs[SPANDATA.DB_OPERATION_NAME] == "SET" + assert attrs["db.redis.key"] == "bar" + else: + events = capture_events() + with start_transaction(): + await connection.set("bar", 1) + + (event,) = events + (span,) = event["spans"] + assert span["op"] == "db.redis" + assert span["description"] == description + assert span["data"] == ApproxDict( + { + SPANDATA.DB_SYSTEM: "redis", + # ClusterNode converts localhost to 127.0.0.1 + SPANDATA.SERVER_ADDRESS: "127.0.0.1", + SPANDATA.SERVER_PORT: 6379, + } + ) + assert span["tags"] == { + "redis.is_cluster": True, + "db.operation": "SET", + "redis.command": "SET", + "redis.key": "bar", } - ) - assert span["tags"] == { - "redis.is_cluster": True, - "db.operation": "SET", - "redis.command": "SET", - "redis.key": "bar", - } +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "send_default_pii, expected_first_ten", [ @@ -110,67 +142,122 @@ async def test_async_basic(sentry_init, capture_events, send_default_pii, descri ) @pytest.mark.asyncio async def test_async_redis_pipeline( - sentry_init, capture_events, send_default_pii, expected_first_ten + sentry_init, + capture_events, + capture_items, + send_default_pii, + expected_first_ten, + span_streaming, ): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, send_default_pii=send_default_pii, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = cluster.RedisCluster(host="localhost", port=6379) - with start_transaction(): - pipeline = connection.pipeline() - 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"] == ApproxDict( - { - "redis.commands": { - "count": 3, - "first_ten": expected_first_ten, - }, - SPANDATA.DB_SYSTEM: "redis", - # ClusterNode converts localhost to 127.0.0.1 - SPANDATA.SERVER_ADDRESS: "127.0.0.1", - SPANDATA.SERVER_PORT: 6379, + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + pipeline = connection.pipeline() + pipeline.get("foo") + pipeline.set("bar", 1) + pipeline.set("baz", 2) + await pipeline.execute() + sentry_sdk.flush() + + assert len(items) == 2 + pipeline_span, parent_span = items[0].payload, items[1].payload + + assert parent_span["name"] == "custom parent" + assert pipeline_span["name"] == "redis.pipeline.execute" + attrs = pipeline_span["attributes"] + assert attrs["sentry.op"] == "db.redis" + assert attrs[SPANDATA.DB_SYSTEM_NAME] == "redis" + assert attrs[SPANDATA.DB_DRIVER_NAME] == "redis-py" + assert attrs[SPANDATA.SERVER_ADDRESS] == "127.0.0.1" + assert attrs[SPANDATA.SERVER_PORT] == 6379 + else: + events = capture_events() + with start_transaction(): + pipeline = connection.pipeline() + 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"] == ApproxDict( + { + "redis.commands": { + "count": 3, + "first_ten": expected_first_ten, + }, + SPANDATA.DB_SYSTEM: "redis", + # ClusterNode converts localhost to 127.0.0.1 + SPANDATA.SERVER_ADDRESS: "127.0.0.1", + SPANDATA.SERVER_PORT: 6379, + } + ) + assert span["tags"] == { + "redis.transaction": False, + "redis.is_cluster": True, } - ) - assert span["tags"] == { - "redis.transaction": False, - "redis.is_cluster": True, - } +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio -async def test_async_span_origin(sentry_init, capture_events): +async def test_async_span_origin( + sentry_init, capture_events, capture_items, span_streaming +): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = cluster.RedisCluster(host="localhost", port=6379) - with start_transaction(name="custom_transaction"): - # default case - await connection.set("somekey", "somevalue") - # pipeline - pipeline = connection.pipeline(transaction=False) - pipeline.get("somekey") - pipeline.set("anotherkey", 1) - await pipeline.execute() + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + # default case + await connection.set("somekey", "somevalue") - (event,) = events + # pipeline + pipeline = connection.pipeline(transaction=False) + pipeline.get("somekey") + pipeline.set("anotherkey", 1) + await pipeline.execute() + sentry_sdk.flush() + + assert len(items) == 3 + set_span, pipeline_span, parent_span = [item.payload for item in items] + + assert parent_span["name"] == "custom parent" + assert parent_span["attributes"]["sentry.origin"] == "manual" + assert set_span["attributes"]["sentry.origin"] == "auto.db.redis" + assert pipeline_span["attributes"]["sentry.origin"] == "auto.db.redis" + else: + events = capture_events() + with start_transaction(name="custom_transaction"): + # default case + await connection.set("somekey", "somevalue") + + # pipeline + pipeline = connection.pipeline(transaction=False) + pipeline.get("somekey") + pipeline.set("anotherkey", 1) + await pipeline.execute() + + (event,) = events - assert event["contexts"]["trace"]["origin"] == "manual" + assert event["contexts"]["trace"]["origin"] == "manual" - for span in event["spans"]: - assert span["origin"] == "auto.db.redis" + for span in event["spans"]: + assert span["origin"] == "auto.db.redis" diff --git a/tests/integrations/redis/test_redis.py b/tests/integrations/redis/test_redis.py index 84c5699d14..dd5293865a 100644 --- a/tests/integrations/redis/test_redis.py +++ b/tests/integrations/redis/test_redis.py @@ -3,6 +3,7 @@ import pytest from fakeredis import FakeStrictRedis +import sentry_sdk from sentry_sdk import capture_message, start_transaction from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.redis import RedisIntegration @@ -42,6 +43,7 @@ def test_basic(sentry_init, capture_events): } +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "is_transaction, send_default_pii, expected_first_ten", [ @@ -50,39 +52,69 @@ def test_basic(sentry_init, capture_events): ], ) def test_redis_pipeline( - sentry_init, capture_events, is_transaction, send_default_pii, expected_first_ten + sentry_init, + capture_events, + capture_items, + is_transaction, + send_default_pii, + expected_first_ten, + span_streaming, ): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, send_default_pii=send_default_pii, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - 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"][SPANDATA.DB_SYSTEM] == "redis" - assert span["data"]["redis.commands"] == { - "count": 3, - "first_ten": expected_first_ten, - } - assert span["tags"] == { - "redis.transaction": is_transaction, - "redis.is_cluster": False, - } + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + pipeline = connection.pipeline(transaction=is_transaction) + pipeline.get("foo") + pipeline.set("bar", 1) + pipeline.set("baz", 2) + pipeline.execute() + sentry_sdk.flush() + + assert len(items) == 2 + pipeline_span, parent_span = items[0].payload, items[1].payload + + assert parent_span["name"] == "custom parent" + assert parent_span["is_segment"] is True + + assert pipeline_span["name"] == "redis.pipeline.execute" + assert pipeline_span["attributes"]["sentry.op"] == "db.redis" + assert pipeline_span["attributes"]["sentry.origin"] == "auto.db.redis" + assert pipeline_span["attributes"][SPANDATA.DB_SYSTEM_NAME] == "redis" + else: + events = capture_events() + 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"][SPANDATA.DB_SYSTEM] == "redis" + assert span["data"]["redis.commands"] == { + "count": 3, + "first_ten": expected_first_ten, + } + assert span["tags"] == { + "redis.transaction": is_transaction, + "redis.is_cluster": False, + } -def test_sensitive_data(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_sensitive_data(sentry_init, capture_events, capture_items, span_streaming): # fakeredis does not support the AUTH command, so we need to mock it with mock.patch( "sentry_sdk.integrations.redis.utils._COMMANDS_INCLUDING_SENSITIVE_DATA", @@ -92,112 +124,213 @@ def test_sensitive_data(sentry_init, capture_events): integrations=[RedisIntegration()], traces_sample_rate=1.0, send_default_pii=True, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeStrictRedis() - with start_transaction(): - connection.get( - "this is super secret" - ) # because fakeredis does not support AUTH we use GET instead - (event,) = events - spans = event["spans"] - assert spans[0]["op"] == "db.redis" - assert spans[0]["description"] == "GET [Filtered]" - - -def test_pii_data_redacted(sentry_init, capture_events): + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection.get("this is super secret") + sentry_sdk.flush() + + assert len(items) == 2 + redis_span, parent_span = items[0].payload, items[1].payload + + assert parent_span["name"] == "custom parent" + assert redis_span["name"] == "GET [Filtered]" + assert redis_span["attributes"]["sentry.op"] == "db.redis" + else: + events = capture_events() + with start_transaction(): + connection.get( + "this is super secret" + ) # because fakeredis does not support AUTH we use GET instead + + (event,) = events + spans = event["spans"] + assert spans[0]["op"] == "db.redis" + assert spans[0]["description"] == "GET [Filtered]" + + +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_pii_data_redacted(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeStrictRedis() - with start_transaction(): - connection.set("somekey1", "my secret string1") - connection.set("somekey2", "my secret string2") - connection.get("somekey2") - connection.delete("somekey1", "somekey2") - (event,) = events - spans = event["spans"] - assert spans[0]["op"] == "db.redis" - assert spans[0]["description"] == "SET 'somekey1' [Filtered]" - assert spans[1]["description"] == "SET 'somekey2' [Filtered]" - assert spans[2]["description"] == "GET 'somekey2'" - assert spans[3]["description"] == "DEL 'somekey1' [Filtered]" + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection.set("somekey1", "my secret string1") + connection.set("somekey2", "my secret string2") + connection.get("somekey2") + connection.delete("somekey1", "somekey2") + sentry_sdk.flush() + + assert len(items) == 5 + set1, set2, get, delete, parent = [item.payload for item in items] + + assert parent["name"] == "custom parent" + assert set1["name"] == "SET 'somekey1' [Filtered]" + assert set1["attributes"]["sentry.op"] == "db.redis" + assert set2["name"] == "SET 'somekey2' [Filtered]" + assert get["name"] == "GET 'somekey2'" + assert delete["name"] == "DEL 'somekey1' [Filtered]" + else: + events = capture_events() + with start_transaction(): + connection.set("somekey1", "my secret string1") + connection.set("somekey2", "my secret string2") + connection.get("somekey2") + connection.delete("somekey1", "somekey2") + (event,) = events + spans = event["spans"] + assert spans[0]["op"] == "db.redis" + assert spans[0]["description"] == "SET 'somekey1' [Filtered]" + assert spans[1]["description"] == "SET 'somekey2' [Filtered]" + assert spans[2]["description"] == "GET 'somekey2'" + assert spans[3]["description"] == "DEL 'somekey1' [Filtered]" -def test_pii_data_sent(sentry_init, capture_events): + +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_pii_data_sent(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, send_default_pii=True, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeStrictRedis() - with start_transaction(): - connection.set("somekey1", "my secret string1") - connection.set("somekey2", "my secret string2") - connection.get("somekey2") - connection.delete("somekey1", "somekey2") - (event,) = events - spans = event["spans"] - assert spans[0]["op"] == "db.redis" - assert spans[0]["description"] == "SET 'somekey1' 'my secret string1'" - assert spans[1]["description"] == "SET 'somekey2' 'my secret string2'" - assert spans[2]["description"] == "GET 'somekey2'" - assert spans[3]["description"] == "DEL 'somekey1' 'somekey2'" + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection.set("somekey1", "my secret string1") + connection.set("somekey2", "my secret string2") + connection.get("somekey2") + connection.delete("somekey1", "somekey2") + sentry_sdk.flush() + + assert len(items) == 5 + set1, set2, get, delete, parent = [item.payload for item in items] + + assert parent["name"] == "custom parent" + assert set1["name"] == "SET 'somekey1' 'my secret string1'" + assert set1["attributes"]["sentry.op"] == "db.redis" + assert set2["name"] == "SET 'somekey2' 'my secret string2'" + assert get["name"] == "GET 'somekey2'" + assert delete["name"] == "DEL 'somekey1' 'somekey2'" + else: + events = capture_events() + with start_transaction(): + connection.set("somekey1", "my secret string1") + connection.set("somekey2", "my secret string2") + connection.get("somekey2") + connection.delete("somekey1", "somekey2") + + (event,) = events + spans = event["spans"] + assert spans[0]["op"] == "db.redis" + assert spans[0]["description"] == "SET 'somekey1' 'my secret string1'" + assert spans[1]["description"] == "SET 'somekey2' 'my secret string2'" + assert spans[2]["description"] == "GET 'somekey2'" + assert spans[3]["description"] == "DEL 'somekey1' 'somekey2'" -def test_no_data_truncation_by_default(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_no_data_truncation_by_default( + sentry_init, capture_events, capture_items, span_streaming +): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, send_default_pii=True, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeStrictRedis() - with start_transaction(): - long_string = "a" * 100000 - connection.set("somekey1", long_string) - short_string = "b" * 10 - connection.set("somekey2", short_string) + long_string = "a" * 100000 + short_string = "b" * 10 - (event,) = events - spans = event["spans"] - assert spans[0]["op"] == "db.redis" - assert spans[0]["description"] == f"SET 'somekey1' '{long_string}'" - assert spans[1]["description"] == f"SET 'somekey2' '{short_string}'" + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection.set("somekey1", long_string) + connection.set("somekey2", short_string) + sentry_sdk.flush() + + assert len(items) == 3 + set1, set2, parent = [item.payload for item in items] + + assert parent["name"] == "custom parent" + assert set1["name"] == f"SET 'somekey1' '{long_string}'" + assert set1["attributes"]["sentry.op"] == "db.redis" + assert set2["name"] == f"SET 'somekey2' '{short_string}'" + else: + events = capture_events() + with start_transaction(): + connection.set("somekey1", long_string) + connection.set("somekey2", short_string) + + (event,) = events + spans = event["spans"] + assert spans[0]["op"] == "db.redis" + assert spans[0]["description"] == f"SET 'somekey1' '{long_string}'" + assert spans[1]["description"] == f"SET 'somekey2' '{short_string}'" -def test_data_truncation_custom(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_data_truncation_custom( + sentry_init, capture_events, capture_items, span_streaming +): sentry_init( integrations=[RedisIntegration(max_data_size=30)], traces_sample_rate=1.0, send_default_pii=True, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeStrictRedis() - with start_transaction(): - long_string = "a" * 100000 - connection.set("somekey1", long_string) - short_string = "b" * 10 - connection.set("somekey2", short_string) - - (event,) = events - spans = event["spans"] - assert spans[0]["op"] == "db.redis" - assert spans[0]["description"] == "SET 'somekey1' '%s..." % ( + long_string = "a" * 100000 + short_string = "b" * 10 + expected_long = "SET 'somekey1' '%s..." % ( long_string[: 30 - len("...") - len("SET 'somekey1' '")], ) - assert spans[1]["description"] == "SET 'somekey2' '%s'" % (short_string,) + expected_short = "SET 'somekey2' '%s'" % (short_string,) + + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection.set("somekey1", long_string) + connection.set("somekey2", short_string) + sentry_sdk.flush() + + assert len(items) == 3 + set1, set2, parent = [item.payload for item in items] + + assert parent["name"] == "custom parent" + assert set1["name"] == expected_long + assert set1["attributes"]["sentry.op"] == "db.redis" + assert set2["name"] == expected_short + else: + events = capture_events() + with start_transaction(): + connection.set("somekey1", long_string) + connection.set("somekey2", short_string) + + (event,) = events + spans = event["spans"] + assert spans[0]["op"] == "db.redis" + assert spans[0]["description"] == expected_long + assert spans[1]["description"] == expected_short def test_breadcrumbs(sentry_init, capture_events): @@ -245,77 +378,153 @@ def test_breadcrumbs(sentry_init, capture_events): } -def test_db_connection_attributes_client(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_db_connection_attributes_client( + sentry_init, capture_events, capture_items, span_streaming +): sentry_init( traces_sample_rate=1.0, integrations=[RedisIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() - with start_transaction(): - connection = FakeStrictRedis(connection_pool=MOCK_CONNECTION_POOL) - connection.get("foobar") + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection = FakeStrictRedis(connection_pool=MOCK_CONNECTION_POOL) + connection.get("foobar") + sentry_sdk.flush() + + assert len(items) == 2 + redis_span, parent_span = items[0].payload, items[1].payload + + assert parent_span["name"] == "custom parent" + assert redis_span["name"] == "GET 'foobar'" + attrs = redis_span["attributes"] + assert attrs["sentry.op"] == "db.redis" + assert attrs[SPANDATA.DB_SYSTEM_NAME] == "redis" + assert attrs[SPANDATA.DB_DRIVER_NAME] == "redis-py" + assert attrs[SPANDATA.DB_NAMESPACE] == "1" + assert attrs[SPANDATA.SERVER_ADDRESS] == "localhost" + assert attrs[SPANDATA.SERVER_PORT] == 63791 + else: + events = capture_events() + with start_transaction(): + connection = FakeStrictRedis(connection_pool=MOCK_CONNECTION_POOL) + connection.get("foobar") - (event,) = events - (span,) = event["spans"] + (event,) = events + (span,) = event["spans"] - assert span["op"] == "db.redis" - assert span["description"] == "GET 'foobar'" - assert span["data"][SPANDATA.DB_SYSTEM] == "redis" - assert span["data"][SPANDATA.DB_DRIVER_NAME] == "redis-py" - assert span["data"][SPANDATA.DB_NAME] == "1" - assert span["data"][SPANDATA.SERVER_ADDRESS] == "localhost" - assert span["data"][SPANDATA.SERVER_PORT] == 63791 + assert span["op"] == "db.redis" + assert span["description"] == "GET 'foobar'" + assert span["data"][SPANDATA.DB_SYSTEM] == "redis" + assert span["data"][SPANDATA.DB_DRIVER_NAME] == "redis-py" + assert span["data"][SPANDATA.DB_NAME] == "1" + assert span["data"][SPANDATA.SERVER_ADDRESS] == "localhost" + assert span["data"][SPANDATA.SERVER_PORT] == 63791 -def test_db_connection_attributes_pipeline(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_db_connection_attributes_pipeline( + sentry_init, capture_events, capture_items, span_streaming +): sentry_init( traces_sample_rate=1.0, integrations=[RedisIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() - with start_transaction(): - connection = FakeStrictRedis(connection_pool=MOCK_CONNECTION_POOL) - pipeline = connection.pipeline(transaction=False) - pipeline.get("foo") - pipeline.set("bar", 1) - pipeline.set("baz", 2) - pipeline.execute() + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection = FakeStrictRedis(connection_pool=MOCK_CONNECTION_POOL) + pipeline = connection.pipeline(transaction=False) + pipeline.get("foo") + pipeline.set("bar", 1) + pipeline.set("baz", 2) + pipeline.execute() + sentry_sdk.flush() + + assert len(items) == 2 + pipeline_span, parent_span = items[0].payload, items[1].payload + + assert parent_span["name"] == "custom parent" + assert pipeline_span["name"] == "redis.pipeline.execute" + attrs = pipeline_span["attributes"] + assert attrs["sentry.op"] == "db.redis" + assert attrs[SPANDATA.DB_SYSTEM_NAME] == "redis" + assert attrs[SPANDATA.DB_DRIVER_NAME] == "redis-py" + assert attrs[SPANDATA.DB_NAMESPACE] == "1" + assert attrs[SPANDATA.SERVER_ADDRESS] == "localhost" + assert attrs[SPANDATA.SERVER_PORT] == 63791 + else: + events = capture_events() + with start_transaction(): + connection = FakeStrictRedis(connection_pool=MOCK_CONNECTION_POOL) + pipeline = connection.pipeline(transaction=False) + pipeline.get("foo") + pipeline.set("bar", 1) + pipeline.set("baz", 2) + pipeline.execute() - (event,) = events - (span,) = event["spans"] + (event,) = events + (span,) = event["spans"] - assert span["op"] == "db.redis" - assert span["description"] == "redis.pipeline.execute" - assert span["data"][SPANDATA.DB_SYSTEM] == "redis" - assert span["data"][SPANDATA.DB_DRIVER_NAME] == "redis-py" - assert span["data"][SPANDATA.DB_NAME] == "1" - assert span["data"][SPANDATA.SERVER_ADDRESS] == "localhost" - assert span["data"][SPANDATA.SERVER_PORT] == 63791 + assert span["op"] == "db.redis" + assert span["description"] == "redis.pipeline.execute" + assert span["data"][SPANDATA.DB_SYSTEM] == "redis" + assert span["data"][SPANDATA.DB_DRIVER_NAME] == "redis-py" + assert span["data"][SPANDATA.DB_NAME] == "1" + assert span["data"][SPANDATA.SERVER_ADDRESS] == "localhost" + assert span["data"][SPANDATA.SERVER_PORT] == 63791 -def test_span_origin(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_span_origin(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[RedisIntegration()], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeStrictRedis() - with start_transaction(name="custom_transaction"): - # default case - connection.set("somekey", "somevalue") - # pipeline - pipeline = connection.pipeline(transaction=False) - pipeline.get("somekey") - pipeline.set("anotherkey", 1) - pipeline.execute() + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + # default case + connection.set("somekey", "somevalue") + + # pipeline + pipeline = connection.pipeline(transaction=False) + pipeline.get("somekey") + pipeline.set("anotherkey", 1) + pipeline.execute() + sentry_sdk.flush() + + assert len(items) == 3 + set_span, pipeline_span, parent_span = [item.payload for item in items] + + assert parent_span["name"] == "custom parent" + assert parent_span["attributes"]["sentry.origin"] == "manual" + assert set_span["attributes"]["sentry.origin"] == "auto.db.redis" + assert pipeline_span["attributes"]["sentry.origin"] == "auto.db.redis" + else: + events = capture_events() + with start_transaction(name="custom_transaction"): + # default case + connection.set("somekey", "somevalue") - (event,) = events + # pipeline + pipeline = connection.pipeline(transaction=False) + pipeline.get("somekey") + pipeline.set("anotherkey", 1) + pipeline.execute() + + (event,) = events - assert event["contexts"]["trace"]["origin"] == "manual" + assert event["contexts"]["trace"]["origin"] == "manual" - for span in event["spans"]: - assert span["origin"] == "auto.db.redis" + for span in event["spans"]: + assert span["origin"] == "auto.db.redis" diff --git a/tests/integrations/redis/test_redis_cache_module.py b/tests/integrations/redis/test_redis_cache_module.py index f118aa53f5..9718c865b7 100644 --- a/tests/integrations/redis/test_redis_cache_module.py +++ b/tests/integrations/redis/test_redis_cache_module.py @@ -5,6 +5,7 @@ import fakeredis from fakeredis import FakeStrictRedis +from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.integrations.redis.utils import _get_safe_key, _key_as_string from sentry_sdk.utils import parse_version @@ -14,26 +15,41 @@ FAKEREDIS_VERSION = parse_version(fakeredis.__version__) -def test_no_cache_basic(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_no_cache_basic(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[ RedisIntegration(), ], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeStrictRedis() - with sentry_sdk.start_transaction(): - connection.get("mycachekey") - (event,) = events - spans = event["spans"] - assert len(spans) == 1 - assert spans[0]["op"] == "db.redis" + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection.get("mycachekey") + sentry_sdk.flush() + + assert len(items) == 2 + db_span, parent_span = items[0].payload, items[1].payload + assert parent_span["name"] == "custom parent" + assert db_span["attributes"]["sentry.op"] == "db.redis" + else: + events = capture_events() + with sentry_sdk.start_transaction(): + connection.get("mycachekey") + + (event,) = events + spans = event["spans"] + assert len(spans) == 1 + assert spans[0]["op"] == "db.redis" -def test_cache_basic(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_cache_basic(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[ RedisIntegration( @@ -41,43 +57,87 @@ def test_cache_basic(sentry_init, capture_events): ), ], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeStrictRedis() - with sentry_sdk.start_transaction(): - connection.hget("mycachekey", "myfield") - connection.get("mycachekey") - connection.set("mycachekey1", "bla") - connection.setex("mycachekey2", 10, "blub") - connection.mget("mycachekey1", "mycachekey2") - (event,) = events - spans = event["spans"] - assert len(spans) == 9 + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection.hget("mycachekey", "myfield") + connection.get("mycachekey") + connection.set("mycachekey1", "bla") + connection.setex("mycachekey2", 10, "blub") + connection.mget("mycachekey1", "mycachekey2") + sentry_sdk.flush() + + # Close order: db spans close before their wrapping cache span, + # and the "custom parent" segment closes last. + assert len(items) == 10 + payloads = [item.payload for item in items] + + # hget: db only (HGET is not a cache command) + assert payloads[0]["attributes"]["sentry.op"] == "db.redis" + assert payloads[0]["attributes"][SPANDATA.DB_OPERATION_NAME] == "HGET" + + # get: db then cache.get + assert payloads[1]["attributes"]["sentry.op"] == "db.redis" + assert payloads[1]["attributes"][SPANDATA.DB_OPERATION_NAME] == "GET" + assert payloads[2]["attributes"]["sentry.op"] == "cache.get" + + # set: db then cache.put + assert payloads[3]["attributes"]["sentry.op"] == "db.redis" + assert payloads[3]["attributes"][SPANDATA.DB_OPERATION_NAME] == "SET" + assert payloads[4]["attributes"]["sentry.op"] == "cache.put" + + # setex: db then cache.put + assert payloads[5]["attributes"]["sentry.op"] == "db.redis" + assert payloads[5]["attributes"][SPANDATA.DB_OPERATION_NAME] == "SETEX" + assert payloads[6]["attributes"]["sentry.op"] == "cache.put" + + # mget: db then cache.get + assert payloads[7]["attributes"]["sentry.op"] == "db.redis" + assert payloads[7]["attributes"][SPANDATA.DB_OPERATION_NAME] == "MGET" + assert payloads[8]["attributes"]["sentry.op"] == "cache.get" + + assert payloads[9]["name"] == "custom parent" + else: + events = capture_events() + with sentry_sdk.start_transaction(): + connection.hget("mycachekey", "myfield") + connection.get("mycachekey") + connection.set("mycachekey1", "bla") + connection.setex("mycachekey2", 10, "blub") + connection.mget("mycachekey1", "mycachekey2") + + (event,) = events + spans = event["spans"] + assert len(spans) == 9 - # no cache support for hget command - assert spans[0]["op"] == "db.redis" - assert spans[0]["tags"]["redis.command"] == "HGET" + # no cache support for hget command + assert spans[0]["op"] == "db.redis" + assert spans[0]["tags"]["redis.command"] == "HGET" - assert spans[1]["op"] == "cache.get" - assert spans[2]["op"] == "db.redis" - assert spans[2]["tags"]["redis.command"] == "GET" + assert spans[1]["op"] == "cache.get" + assert spans[2]["op"] == "db.redis" + assert spans[2]["tags"]["redis.command"] == "GET" - assert spans[3]["op"] == "cache.put" - assert spans[4]["op"] == "db.redis" - assert spans[4]["tags"]["redis.command"] == "SET" + assert spans[3]["op"] == "cache.put" + assert spans[4]["op"] == "db.redis" + assert spans[4]["tags"]["redis.command"] == "SET" - assert spans[5]["op"] == "cache.put" - assert spans[6]["op"] == "db.redis" - assert spans[6]["tags"]["redis.command"] == "SETEX" + assert spans[5]["op"] == "cache.put" + assert spans[6]["op"] == "db.redis" + assert spans[6]["tags"]["redis.command"] == "SETEX" - assert spans[7]["op"] == "cache.get" - assert spans[8]["op"] == "db.redis" - assert spans[8]["tags"]["redis.command"] == "MGET" + assert spans[7]["op"] == "cache.get" + assert spans[8]["op"] == "db.redis" + assert spans[8]["tags"]["redis.command"] == "MGET" -def test_cache_keys(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_cache_keys(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[ RedisIntegration( @@ -85,37 +145,74 @@ def test_cache_keys(sentry_init, capture_events): ), ], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeStrictRedis() - with sentry_sdk.start_transaction(): - connection.get("somethingelse") - connection.get("blub") - connection.get("blubkeything") - connection.get("bl") - - (event,) = events - spans = event["spans"] - assert len(spans) == 6 - assert spans[0]["op"] == "db.redis" - assert spans[0]["description"] == "GET 'somethingelse'" - assert spans[1]["op"] == "cache.get" - assert spans[1]["description"] == "blub" - assert spans[2]["op"] == "db.redis" - assert spans[2]["description"] == "GET 'blub'" - - assert spans[3]["op"] == "cache.get" - assert spans[3]["description"] == "blubkeything" - assert spans[4]["op"] == "db.redis" - assert spans[4]["description"] == "GET 'blubkeything'" - - assert spans[5]["op"] == "db.redis" - assert spans[5]["description"] == "GET 'bl'" - - -def test_cache_data(sentry_init, capture_events): + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection.get("somethingelse") + connection.get("blub") + connection.get("blubkeything") + connection.get("bl") + sentry_sdk.flush() + + assert len(items) == 7 + payloads = [item.payload for item in items] + + # somethingelse: db only + assert payloads[0]["attributes"]["sentry.op"] == "db.redis" + assert payloads[0]["name"] == "GET 'somethingelse'" + + # blub: db then cache.get + assert payloads[1]["attributes"]["sentry.op"] == "db.redis" + assert payloads[1]["name"] == "GET 'blub'" + assert payloads[2]["attributes"]["sentry.op"] == "cache.get" + assert payloads[2]["name"] == "blub" + + # blubkeything: db then cache.get + assert payloads[3]["attributes"]["sentry.op"] == "db.redis" + assert payloads[3]["name"] == "GET 'blubkeything'" + assert payloads[4]["attributes"]["sentry.op"] == "cache.get" + assert payloads[4]["name"] == "blubkeything" + + # bl: db only (no prefix match) + assert payloads[5]["attributes"]["sentry.op"] == "db.redis" + assert payloads[5]["name"] == "GET 'bl'" + + assert payloads[6]["name"] == "custom parent" + else: + events = capture_events() + with sentry_sdk.start_transaction(): + connection.get("somethingelse") + connection.get("blub") + connection.get("blubkeything") + connection.get("bl") + + (event,) = events + spans = event["spans"] + assert len(spans) == 6 + assert spans[0]["op"] == "db.redis" + assert spans[0]["description"] == "GET 'somethingelse'" + + assert spans[1]["op"] == "cache.get" + assert spans[1]["description"] == "blub" + assert spans[2]["op"] == "db.redis" + assert spans[2]["description"] == "GET 'blub'" + + assert spans[3]["op"] == "cache.get" + assert spans[3]["description"] == "blubkeything" + assert spans[4]["op"] == "db.redis" + assert spans[4]["description"] == "GET 'blubkeything'" + + assert spans[5]["op"] == "db.redis" + assert spans[5]["description"] == "GET 'bl'" + + +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_cache_data(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[ RedisIntegration( @@ -123,82 +220,152 @@ def test_cache_data(sentry_init, capture_events): ), ], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() - - connection = FakeStrictRedis(host="mycacheserver.io", port=6378) - with sentry_sdk.start_transaction(): - connection.get("mycachekey") - connection.set("mycachekey", "事实胜于雄辩") - connection.get("mycachekey") - - (event,) = events - spans = event["spans"] - - assert len(spans) == 6 - - assert spans[0]["op"] == "cache.get" - assert spans[0]["description"] == "mycachekey" - assert spans[0]["data"]["cache.key"] == [ - "mycachekey", - ] - assert spans[0]["data"]["cache.hit"] == False # noqa: E712 - assert "cache.item_size" not in spans[0]["data"] - # very old fakeredis can not handle port and/or host. - # only applicable for Redis v3 - if FAKEREDIS_VERSION <= (2, 7, 1): - assert "network.peer.port" not in spans[0]["data"] - else: - assert spans[0]["data"]["network.peer.port"] == 6378 - if FAKEREDIS_VERSION <= (1, 7, 1): - assert "network.peer.address" not in spans[0]["data"] - else: - assert spans[0]["data"]["network.peer.address"] == "mycacheserver.io" - - assert spans[1]["op"] == "db.redis" # we ignore db spans in this test. - - assert spans[2]["op"] == "cache.put" - assert spans[2]["description"] == "mycachekey" - assert spans[2]["data"]["cache.key"] == [ - "mycachekey", - ] - assert "cache.hit" not in spans[1]["data"] - assert spans[2]["data"]["cache.item_size"] == 18 - # very old fakeredis can not handle port. - # only used with redis v3 - if FAKEREDIS_VERSION <= (2, 7, 1): - assert "network.peer.port" not in spans[2]["data"] - else: - assert spans[2]["data"]["network.peer.port"] == 6378 - if FAKEREDIS_VERSION <= (1, 7, 1): - assert "network.peer.address" not in spans[2]["data"] - else: - assert spans[2]["data"]["network.peer.address"] == "mycacheserver.io" - - assert spans[3]["op"] == "db.redis" # we ignore db spans in this test. - - assert spans[4]["op"] == "cache.get" - assert spans[4]["description"] == "mycachekey" - assert spans[4]["data"]["cache.key"] == [ - "mycachekey", - ] - assert spans[4]["data"]["cache.hit"] == True # noqa: E712 - assert spans[4]["data"]["cache.item_size"] == 18 - # very old fakeredis can not handle port. - # only used with redis v3 - if FAKEREDIS_VERSION <= (2, 7, 1): - assert "network.peer.port" not in spans[4]["data"] - else: - assert spans[4]["data"]["network.peer.port"] == 6378 - if FAKEREDIS_VERSION <= (1, 7, 1): - assert "network.peer.address" not in spans[4]["data"] - else: - assert spans[4]["data"]["network.peer.address"] == "mycacheserver.io" - assert spans[5]["op"] == "db.redis" # we ignore db spans in this test. - - -def test_cache_prefixes(sentry_init, capture_events): + # Use a unique host per parametrized run so fakeredis (which shares state + # keyed by host:port) doesn't leak the SET from a prior run into this one. + host = f"mycacheserver-{uuid.uuid4().hex}.io" + connection = FakeStrictRedis(host=host, port=6378) + + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection.get("mycachekey") + connection.set("mycachekey", "事实胜于雄辩") + connection.get("mycachekey") + sentry_sdk.flush() + + # Close order: db then cache for each command, then parent + assert len(items) == 7 + payloads = [item.payload for item in items] + + # First get (miss) + assert payloads[0]["attributes"]["sentry.op"] == "db.redis" + cache_get_miss = payloads[1] + assert cache_get_miss["attributes"]["sentry.op"] == "cache.get" + assert cache_get_miss["name"] == "mycachekey" + assert cache_get_miss["attributes"]["cache.key"] == ["mycachekey"] + assert cache_get_miss["attributes"]["cache.hit"] is False + assert "cache.item_size" not in cache_get_miss["attributes"] + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in cache_get_miss["attributes"] + else: + assert cache_get_miss["attributes"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in cache_get_miss["attributes"] + else: + assert cache_get_miss["attributes"]["network.peer.address"] == host + + # Set + assert payloads[2]["attributes"]["sentry.op"] == "db.redis" + cache_put = payloads[3] + assert cache_put["attributes"]["sentry.op"] == "cache.put" + assert cache_put["name"] == "mycachekey" + assert cache_put["attributes"]["cache.key"] == ["mycachekey"] + assert "cache.hit" not in cache_put["attributes"] + assert cache_put["attributes"]["cache.item_size"] == 18 + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in cache_put["attributes"] + else: + assert cache_put["attributes"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in cache_put["attributes"] + else: + assert cache_put["attributes"]["network.peer.address"] == host + + # Second get (hit) + assert payloads[4]["attributes"]["sentry.op"] == "db.redis" + cache_get_hit = payloads[5] + assert cache_get_hit["attributes"]["sentry.op"] == "cache.get" + assert cache_get_hit["attributes"]["cache.key"] == ["mycachekey"] + assert cache_get_hit["attributes"]["cache.hit"] is True + assert cache_get_hit["attributes"]["cache.item_size"] == 18 + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in cache_get_hit["attributes"] + else: + assert cache_get_hit["attributes"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in cache_get_hit["attributes"] + else: + assert cache_get_hit["attributes"]["network.peer.address"] == host + + assert payloads[6]["name"] == "custom parent" + else: + events = capture_events() + with sentry_sdk.start_transaction(): + connection.get("mycachekey") + connection.set("mycachekey", "事实胜于雄辩") + connection.get("mycachekey") + + (event,) = events + spans = event["spans"] + + assert len(spans) == 6 + + assert spans[0]["op"] == "cache.get" + assert spans[0]["description"] == "mycachekey" + assert spans[0]["data"]["cache.key"] == [ + "mycachekey", + ] + assert spans[0]["data"]["cache.hit"] == False # noqa: E712 + assert "cache.item_size" not in spans[0]["data"] + # very old fakeredis can not handle port and/or host. + # only applicable for Redis v3 + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in spans[0]["data"] + else: + assert spans[0]["data"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in spans[0]["data"] + else: + assert spans[0]["data"]["network.peer.address"] == host + + assert spans[1]["op"] == "db.redis" # we ignore db spans in this test. + + assert spans[2]["op"] == "cache.put" + assert spans[2]["description"] == "mycachekey" + assert spans[2]["data"]["cache.key"] == [ + "mycachekey", + ] + assert "cache.hit" not in spans[1]["data"] + assert spans[2]["data"]["cache.item_size"] == 18 + # very old fakeredis can not handle port. + # only used with redis v3 + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in spans[2]["data"] + else: + assert spans[2]["data"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in spans[2]["data"] + else: + assert spans[2]["data"]["network.peer.address"] == host + + assert spans[3]["op"] == "db.redis" # we ignore db spans in this test. + + assert spans[4]["op"] == "cache.get" + assert spans[4]["description"] == "mycachekey" + assert spans[4]["data"]["cache.key"] == [ + "mycachekey", + ] + assert spans[4]["data"]["cache.hit"] == True # noqa: E712 + assert spans[4]["data"]["cache.item_size"] == 18 + # very old fakeredis can not handle port. + # only used with redis v3 + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in spans[4]["data"] + else: + assert spans[4]["data"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in spans[4]["data"] + else: + assert spans[4]["data"]["network.peer.address"] == host + + assert spans[5]["op"] == "db.redis" # we ignore db spans in this test. + + +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_cache_prefixes(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[ RedisIntegration( @@ -206,33 +373,64 @@ def test_cache_prefixes(sentry_init, capture_events): ), ], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeStrictRedis() - with sentry_sdk.start_transaction(): - connection.mget("yes", "no") - connection.mget("no", 1, "yes") - connection.mget("no", "yes.1", "yes.2") - connection.mget("no.1", "no.2", "no.3") - connection.mget("no.1", "no.2", "no.actually.yes") - connection.mget(b"no.3", b"yes.5") - connection.mget(uuid.uuid4().bytes) - connection.mget(uuid.uuid4().bytes, "yes") - - (event,) = events - - spans = event["spans"] - assert len(spans) == 13 # 8 db spans + 5 cache spans - - cache_spans = [span for span in spans if span["op"] == "cache.get"] - assert len(cache_spans) == 5 - - assert cache_spans[0]["description"] == "yes, no" - assert cache_spans[1]["description"] == "no, 1, yes" - assert cache_spans[2]["description"] == "no, yes.1, yes.2" - assert cache_spans[3]["description"] == "no.3, yes.5" - assert cache_spans[4]["description"] == ", yes" + + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + connection.mget("yes", "no") + connection.mget("no", 1, "yes") + connection.mget("no", "yes.1", "yes.2") + connection.mget("no.1", "no.2", "no.3") + connection.mget("no.1", "no.2", "no.actually.yes") + connection.mget(b"no.3", b"yes.5") + connection.mget(uuid.uuid4().bytes) + connection.mget(uuid.uuid4().bytes, "yes") + sentry_sdk.flush() + + # 8 db spans + 5 cache spans + 1 parent + assert len(items) == 14 + payloads = [item.payload for item in items] + assert payloads[-1]["name"] == "custom parent" + + cache_spans = [ + p for p in payloads if p["attributes"].get("sentry.op") == "cache.get" + ] + assert len(cache_spans) == 5 + + assert cache_spans[0]["name"] == "yes, no" + assert cache_spans[1]["name"] == "no, 1, yes" + assert cache_spans[2]["name"] == "no, yes.1, yes.2" + assert cache_spans[3]["name"] == "no.3, yes.5" + assert cache_spans[4]["name"] == ", yes" + else: + events = capture_events() + with sentry_sdk.start_transaction(): + connection.mget("yes", "no") + connection.mget("no", 1, "yes") + connection.mget("no", "yes.1", "yes.2") + connection.mget("no.1", "no.2", "no.3") + connection.mget("no.1", "no.2", "no.actually.yes") + connection.mget(b"no.3", b"yes.5") + connection.mget(uuid.uuid4().bytes) + connection.mget(uuid.uuid4().bytes, "yes") + + (event,) = events + + spans = event["spans"] + assert len(spans) == 13 # 8 db spans + 5 cache spans + + cache_spans = [span for span in spans if span["op"] == "cache.get"] + assert len(cache_spans) == 5 + + assert cache_spans[0]["description"] == "yes, no" + assert cache_spans[1]["description"] == "no, 1, yes" + assert cache_spans[2]["description"] == "no, yes.1, yes.2" + assert cache_spans[3]["description"] == "no.3, yes.5" + assert cache_spans[4]["description"] == ", yes" @pytest.mark.parametrize( diff --git a/tests/integrations/redis/test_redis_cache_module_async.py b/tests/integrations/redis/test_redis_cache_module_async.py index d607f92fbd..ac5538c077 100644 --- a/tests/integrations/redis/test_redis_cache_module_async.py +++ b/tests/integrations/redis/test_redis_cache_module_async.py @@ -1,3 +1,5 @@ +import uuid + import pytest try: @@ -20,28 +22,45 @@ FAKEREDIS_VERSION = parse_version(fakeredis.__version__) +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio -async def test_no_cache_basic(sentry_init, capture_events): +async def test_no_cache_basic( + sentry_init, capture_events, capture_items, span_streaming +): sentry_init( integrations=[ RedisIntegration(), ], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeRedisAsync() - with sentry_sdk.start_transaction(): - await connection.get("myasynccachekey") - (event,) = events - spans = event["spans"] - assert len(spans) == 1 - assert spans[0]["op"] == "db.redis" + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + await connection.get("myasynccachekey") + sentry_sdk.flush() + + assert len(items) == 2 + db_span, parent_span = items[0].payload, items[1].payload + assert parent_span["name"] == "custom parent" + assert db_span["attributes"]["sentry.op"] == "db.redis" + else: + events = capture_events() + with sentry_sdk.start_transaction(): + await connection.get("myasynccachekey") + (event,) = events + spans = event["spans"] + assert len(spans) == 1 + assert spans[0]["op"] == "db.redis" + +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio -async def test_cache_basic(sentry_init, capture_events): +async def test_cache_basic(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[ RedisIntegration( @@ -49,23 +68,38 @@ async def test_cache_basic(sentry_init, capture_events): ), ], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeRedisAsync() - with sentry_sdk.start_transaction(): - await connection.get("myasynccachekey") - (event,) = events - spans = event["spans"] - assert len(spans) == 2 + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + await connection.get("myasynccachekey") + sentry_sdk.flush() + + assert len(items) == 3 + db_span, cache_span, parent_span = [item.payload for item in items] + assert parent_span["name"] == "custom parent" + assert db_span["attributes"]["sentry.op"] == "db.redis" + assert cache_span["attributes"]["sentry.op"] == "cache.get" + else: + events = capture_events() + with sentry_sdk.start_transaction(): + await connection.get("myasynccachekey") + + (event,) = events + spans = event["spans"] + assert len(spans) == 2 - assert spans[0]["op"] == "cache.get" - assert spans[1]["op"] == "db.redis" + assert spans[0]["op"] == "cache.get" + assert spans[1]["op"] == "db.redis" +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio -async def test_cache_keys(sentry_init, capture_events): +async def test_cache_keys(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[ RedisIntegration( @@ -73,38 +107,75 @@ async def test_cache_keys(sentry_init, capture_events): ), ], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() connection = FakeRedisAsync() - with sentry_sdk.start_transaction(): - await connection.get("asomethingelse") - await connection.get("ablub") - await connection.get("ablubkeything") - await connection.get("abl") - - (event,) = events - spans = event["spans"] - assert len(spans) == 6 - assert spans[0]["op"] == "db.redis" - assert spans[0]["description"] == "GET 'asomethingelse'" - - assert spans[1]["op"] == "cache.get" - assert spans[1]["description"] == "ablub" - assert spans[2]["op"] == "db.redis" - assert spans[2]["description"] == "GET 'ablub'" - - assert spans[3]["op"] == "cache.get" - assert spans[3]["description"] == "ablubkeything" - assert spans[4]["op"] == "db.redis" - assert spans[4]["description"] == "GET 'ablubkeything'" - - assert spans[5]["op"] == "db.redis" - assert spans[5]["description"] == "GET 'abl'" - + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + await connection.get("asomethingelse") + await connection.get("ablub") + await connection.get("ablubkeything") + await connection.get("abl") + sentry_sdk.flush() + + assert len(items) == 7 + payloads = [item.payload for item in items] + + # asomethingelse: db only + assert payloads[0]["attributes"]["sentry.op"] == "db.redis" + assert payloads[0]["name"] == "GET 'asomethingelse'" + + # ablub: db then cache.get + assert payloads[1]["attributes"]["sentry.op"] == "db.redis" + assert payloads[1]["name"] == "GET 'ablub'" + assert payloads[2]["attributes"]["sentry.op"] == "cache.get" + assert payloads[2]["name"] == "ablub" + + # ablubkeything: db then cache.get + assert payloads[3]["attributes"]["sentry.op"] == "db.redis" + assert payloads[3]["name"] == "GET 'ablubkeything'" + assert payloads[4]["attributes"]["sentry.op"] == "cache.get" + assert payloads[4]["name"] == "ablubkeything" + + # abl: db only (no prefix match) + assert payloads[5]["attributes"]["sentry.op"] == "db.redis" + assert payloads[5]["name"] == "GET 'abl'" + + assert payloads[6]["name"] == "custom parent" + else: + events = capture_events() + with sentry_sdk.start_transaction(): + await connection.get("asomethingelse") + await connection.get("ablub") + await connection.get("ablubkeything") + await connection.get("abl") + + (event,) = events + spans = event["spans"] + assert len(spans) == 6 + assert spans[0]["op"] == "db.redis" + assert spans[0]["description"] == "GET 'asomethingelse'" + + assert spans[1]["op"] == "cache.get" + assert spans[1]["description"] == "ablub" + assert spans[2]["op"] == "db.redis" + assert spans[2]["description"] == "GET 'ablub'" + + assert spans[3]["op"] == "cache.get" + assert spans[3]["description"] == "ablubkeything" + assert spans[4]["op"] == "db.redis" + assert spans[4]["description"] == "GET 'ablubkeything'" + + assert spans[5]["op"] == "db.redis" + assert spans[5]["description"] == "GET 'abl'" + + +@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio -async def test_cache_data(sentry_init, capture_events): +async def test_cache_data(sentry_init, capture_events, capture_items, span_streaming): sentry_init( integrations=[ RedisIntegration( @@ -112,76 +183,144 @@ async def test_cache_data(sentry_init, capture_events): ), ], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - events = capture_events() - - connection = FakeRedisAsync(host="mycacheserver.io", port=6378) - with sentry_sdk.start_transaction(): - await connection.get("myasynccachekey") - await connection.set("myasynccachekey", "事实胜于雄辩") - await connection.get("myasynccachekey") - - (event,) = events - spans = event["spans"] - - assert len(spans) == 6 - - assert spans[0]["op"] == "cache.get" - assert spans[0]["description"] == "myasynccachekey" - assert spans[0]["data"]["cache.key"] == [ - "myasynccachekey", - ] - assert spans[0]["data"]["cache.hit"] == False # noqa: E712 - assert "cache.item_size" not in spans[0]["data"] - # very old fakeredis can not handle port and/or host. - # only applicable for Redis v3 - if FAKEREDIS_VERSION <= (2, 7, 1): - assert "network.peer.port" not in spans[0]["data"] - else: - assert spans[0]["data"]["network.peer.port"] == 6378 - if FAKEREDIS_VERSION <= (1, 7, 1): - assert "network.peer.address" not in spans[0]["data"] - else: - assert spans[0]["data"]["network.peer.address"] == "mycacheserver.io" - - assert spans[1]["op"] == "db.redis" # we ignore db spans in this test. - - assert spans[2]["op"] == "cache.put" - assert spans[2]["description"] == "myasynccachekey" - assert spans[2]["data"]["cache.key"] == [ - "myasynccachekey", - ] - assert "cache.hit" not in spans[1]["data"] - assert spans[2]["data"]["cache.item_size"] == 18 - # very old fakeredis can not handle port. - # only used with redis v3 - if FAKEREDIS_VERSION <= (2, 7, 1): - assert "network.peer.port" not in spans[2]["data"] - else: - assert spans[2]["data"]["network.peer.port"] == 6378 - if FAKEREDIS_VERSION <= (1, 7, 1): - assert "network.peer.address" not in spans[2]["data"] - else: - assert spans[2]["data"]["network.peer.address"] == "mycacheserver.io" - - assert spans[3]["op"] == "db.redis" # we ignore db spans in this test. - - assert spans[4]["op"] == "cache.get" - assert spans[4]["description"] == "myasynccachekey" - assert spans[4]["data"]["cache.key"] == [ - "myasynccachekey", - ] - assert spans[4]["data"]["cache.hit"] == True # noqa: E712 - assert spans[4]["data"]["cache.item_size"] == 18 - # very old fakeredis can not handle port. - # only used with redis v3 - if FAKEREDIS_VERSION <= (2, 7, 1): - assert "network.peer.port" not in spans[4]["data"] - else: - assert spans[4]["data"]["network.peer.port"] == 6378 - if FAKEREDIS_VERSION <= (1, 7, 1): - assert "network.peer.address" not in spans[4]["data"] - else: - assert spans[4]["data"]["network.peer.address"] == "mycacheserver.io" - assert spans[5]["op"] == "db.redis" # we ignore db spans in this test. + # Use a unique host per parametrized run so fakeredis (which shares state + # keyed by host:port) doesn't leak the SET from a prior run into this one. + host = f"mycacheserver-{uuid.uuid4().hex}.io" + connection = FakeRedisAsync(host=host, port=6378) + + if span_streaming: + items = capture_items("span") + with sentry_sdk.traces.start_span(name="custom parent"): + await connection.get("myasynccachekey") + await connection.set("myasynccachekey", "事实胜于雄辩") + await connection.get("myasynccachekey") + sentry_sdk.flush() + + assert len(items) == 7 + payloads = [item.payload for item in items] + + # First get (miss) + assert payloads[0]["attributes"]["sentry.op"] == "db.redis" + cache_get_miss = payloads[1] + assert cache_get_miss["attributes"]["sentry.op"] == "cache.get" + assert cache_get_miss["name"] == "myasynccachekey" + assert cache_get_miss["attributes"]["cache.key"] == ["myasynccachekey"] + assert cache_get_miss["attributes"]["cache.hit"] is False + assert "cache.item_size" not in cache_get_miss["attributes"] + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in cache_get_miss["attributes"] + else: + assert cache_get_miss["attributes"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in cache_get_miss["attributes"] + else: + assert cache_get_miss["attributes"]["network.peer.address"] == host + + # Set + assert payloads[2]["attributes"]["sentry.op"] == "db.redis" + cache_put = payloads[3] + assert cache_put["attributes"]["sentry.op"] == "cache.put" + assert cache_put["name"] == "myasynccachekey" + assert cache_put["attributes"]["cache.key"] == ["myasynccachekey"] + assert "cache.hit" not in cache_put["attributes"] + assert cache_put["attributes"]["cache.item_size"] == 18 + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in cache_put["attributes"] + else: + assert cache_put["attributes"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in cache_put["attributes"] + else: + assert cache_put["attributes"]["network.peer.address"] == host + + # Second get (hit) + assert payloads[4]["attributes"]["sentry.op"] == "db.redis" + cache_get_hit = payloads[5] + assert cache_get_hit["attributes"]["sentry.op"] == "cache.get" + assert cache_get_hit["attributes"]["cache.key"] == ["myasynccachekey"] + assert cache_get_hit["attributes"]["cache.hit"] is True + assert cache_get_hit["attributes"]["cache.item_size"] == 18 + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in cache_get_hit["attributes"] + else: + assert cache_get_hit["attributes"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in cache_get_hit["attributes"] + else: + assert cache_get_hit["attributes"]["network.peer.address"] == host + + assert payloads[6]["name"] == "custom parent" + else: + events = capture_events() + with sentry_sdk.start_transaction(): + await connection.get("myasynccachekey") + await connection.set("myasynccachekey", "事实胜于雄辩") + await connection.get("myasynccachekey") + + (event,) = events + spans = event["spans"] + + assert len(spans) == 6 + + assert spans[0]["op"] == "cache.get" + assert spans[0]["description"] == "myasynccachekey" + assert spans[0]["data"]["cache.key"] == [ + "myasynccachekey", + ] + assert spans[0]["data"]["cache.hit"] == False # noqa: E712 + assert "cache.item_size" not in spans[0]["data"] + # very old fakeredis can not handle port and/or host. + # only applicable for Redis v3 + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in spans[0]["data"] + else: + assert spans[0]["data"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in spans[0]["data"] + else: + assert spans[0]["data"]["network.peer.address"] == host + + assert spans[1]["op"] == "db.redis" # we ignore db spans in this test. + + assert spans[2]["op"] == "cache.put" + assert spans[2]["description"] == "myasynccachekey" + assert spans[2]["data"]["cache.key"] == [ + "myasynccachekey", + ] + assert "cache.hit" not in spans[1]["data"] + assert spans[2]["data"]["cache.item_size"] == 18 + # very old fakeredis can not handle port. + # only used with redis v3 + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in spans[2]["data"] + else: + assert spans[2]["data"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in spans[2]["data"] + else: + assert spans[2]["data"]["network.peer.address"] == host + + assert spans[3]["op"] == "db.redis" # we ignore db spans in this test. + + assert spans[4]["op"] == "cache.get" + assert spans[4]["description"] == "myasynccachekey" + assert spans[4]["data"]["cache.key"] == [ + "myasynccachekey", + ] + assert spans[4]["data"]["cache.hit"] == True # noqa: E712 + assert spans[4]["data"]["cache.item_size"] == 18 + # very old fakeredis can not handle port. + # only used with redis v3 + if FAKEREDIS_VERSION <= (2, 7, 1): + assert "network.peer.port" not in spans[4]["data"] + else: + assert spans[4]["data"]["network.peer.port"] == 6378 + if FAKEREDIS_VERSION <= (1, 7, 1): + assert "network.peer.address" not in spans[4]["data"] + else: + assert spans[4]["data"]["network.peer.address"] == host + + assert spans[5]["op"] == "db.redis" # we ignore db spans in this test.