From c5cfd87bdb25317e1c50aa075b96f82c74db0d9b Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Tue, 23 Jun 2026 14:58:10 -0400 Subject: [PATCH] feat(redis): Set db.query.text and cache.key span attributes When using span streaming, set SPANDATA.DB_QUERY_TEXT on db.redis spans and SPANDATA.CACHE_KEY on cache spans. These were already set as the span description/name but were missing from the attributes dict in the streaming path. Refs PY-2549 Co-Authored-By: Claude Sonnet 4.6 --- .../integrations/redis/_async_common.py | 4 ++- sentry_sdk/integrations/redis/_sync_common.py | 4 ++- tests/integrations/redis/test_redis.py | 25 +++++++++++++++++++ .../redis/test_redis_cache_module.py | 9 +++++++ .../redis/test_redis_cache_module_async.py | 4 +++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/redis/_async_common.py b/sentry_sdk/integrations/redis/_async_common.py index 310d18eba4..48c548f050 100644 --- a/sentry_sdk/integrations/redis/_async_common.py +++ b/sentry_sdk/integrations/redis/_async_common.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING import sentry_sdk -from sentry_sdk.consts import OP +from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN from sentry_sdk.integrations.redis.modules.caches import ( _compile_cache_span_properties, @@ -117,6 +117,7 @@ async def _sentry_execute_command( attributes={ "sentry.op": cache_properties["op"], "sentry.origin": SPAN_ORIGIN, + SPANDATA.CACHE_KEY: cache_properties["description"], }, ) else: @@ -136,6 +137,7 @@ async def _sentry_execute_command( attributes={ "sentry.op": db_properties["op"], "sentry.origin": SPAN_ORIGIN, + SPANDATA.DB_QUERY_TEXT: db_properties["description"], }, ) else: diff --git a/sentry_sdk/integrations/redis/_sync_common.py b/sentry_sdk/integrations/redis/_sync_common.py index 889b602da3..181e4c1826 100644 --- a/sentry_sdk/integrations/redis/_sync_common.py +++ b/sentry_sdk/integrations/redis/_sync_common.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING import sentry_sdk -from sentry_sdk.consts import OP +from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN from sentry_sdk.integrations.redis.modules.caches import ( _compile_cache_span_properties, @@ -116,6 +116,7 @@ def sentry_patched_execute_command( attributes={ "sentry.op": cache_properties["op"], "sentry.origin": SPAN_ORIGIN, + SPANDATA.CACHE_KEY: cache_properties["description"], }, ) else: @@ -135,6 +136,7 @@ def sentry_patched_execute_command( attributes={ "sentry.op": db_properties["op"], "sentry.origin": SPAN_ORIGIN, + SPANDATA.DB_QUERY_TEXT: db_properties["description"], }, ) else: diff --git a/tests/integrations/redis/test_redis.py b/tests/integrations/redis/test_redis.py index b5c83f9412..8bca575f05 100644 --- a/tests/integrations/redis/test_redis.py +++ b/tests/integrations/redis/test_redis.py @@ -139,6 +139,7 @@ def test_sensitive_data(sentry_init, capture_events, capture_items, span_streami assert parent_span["name"] == "custom parent" assert redis_span["name"] == "GET [Filtered]" + assert redis_span["attributes"][SPANDATA.DB_QUERY_TEXT] == "GET [Filtered]" assert redis_span["attributes"]["sentry.op"] == "db.redis" else: events = capture_events() @@ -177,8 +178,10 @@ def test_pii_data_redacted(sentry_init, capture_events, capture_items, span_stre assert parent["name"] == "custom parent" assert set1["name"] == "SET 'somekey1' [Filtered]" + assert set1["attributes"][SPANDATA.DB_QUERY_TEXT] == "SET 'somekey1' [Filtered]" assert set1["attributes"]["sentry.op"] == "db.redis" assert set2["name"] == "SET 'somekey2' [Filtered]" + assert set2["attributes"][SPANDATA.DB_QUERY_TEXT] == "SET 'somekey2' [Filtered]" assert get["name"] == "GET 'somekey2'" assert delete["name"] == "DEL 'somekey1' [Filtered]" else: @@ -223,8 +226,16 @@ def test_pii_data_sent(sentry_init, capture_events, capture_items, span_streamin assert parent["name"] == "custom parent" assert set1["name"] == "SET 'somekey1' 'my secret string1'" + assert ( + set1["attributes"][SPANDATA.DB_QUERY_TEXT] + == "SET 'somekey1' 'my secret string1'" + ) assert set1["attributes"]["sentry.op"] == "db.redis" assert set2["name"] == "SET 'somekey2' 'my secret string2'" + assert ( + set2["attributes"][SPANDATA.DB_QUERY_TEXT] + == "SET 'somekey2' 'my secret string2'" + ) assert get["name"] == "GET 'somekey2'" assert delete["name"] == "DEL 'somekey1' 'somekey2'" else: @@ -271,8 +282,16 @@ def test_no_data_truncation_by_default( assert parent["name"] == "custom parent" assert set1["name"] == f"SET 'somekey1' '{long_string}'" + assert ( + set1["attributes"][SPANDATA.DB_QUERY_TEXT] + == f"SET 'somekey1' '{long_string}'" + ) assert set1["attributes"]["sentry.op"] == "db.redis" assert set2["name"] == f"SET 'somekey2' '{short_string}'" + assert ( + set2["attributes"][SPANDATA.DB_QUERY_TEXT] + == f"SET 'somekey2' '{short_string}'" + ) else: events = capture_events() with start_transaction(): @@ -317,8 +336,10 @@ def test_data_truncation_custom( assert parent["name"] == "custom parent" assert set1["name"] == expected_long + assert set1["attributes"][SPANDATA.DB_QUERY_TEXT] == expected_long assert set1["attributes"]["sentry.op"] == "db.redis" assert set2["name"] == expected_short + assert set2["attributes"][SPANDATA.DB_QUERY_TEXT] == expected_short else: events = capture_events() with start_transaction(): @@ -401,6 +422,7 @@ def test_db_connection_attributes_client( assert redis_span["name"] == "GET 'foobar'" attrs = redis_span["attributes"] assert attrs["sentry.op"] == "db.redis" + assert attrs[SPANDATA.DB_QUERY_TEXT] == "GET 'foobar'" assert attrs[SPANDATA.DB_SYSTEM_NAME] == "redis" assert attrs[SPANDATA.DB_DRIVER_NAME] == "redis-py" assert attrs[SPANDATA.DB_NAMESPACE] == "1" @@ -508,6 +530,9 @@ def test_span_origin(sentry_init, capture_events, capture_items, span_streaming) assert parent_span["name"] == "custom parent" assert parent_span["attributes"]["sentry.origin"] == "manual" assert set_span["attributes"]["sentry.origin"] == "auto.db.redis" + assert ( + set_span["attributes"][SPANDATA.DB_QUERY_TEXT] == "SET 'somekey' [Filtered]" + ) assert pipeline_span["attributes"]["sentry.origin"] == "auto.db.redis" else: events = capture_events() diff --git a/tests/integrations/redis/test_redis_cache_module.py b/tests/integrations/redis/test_redis_cache_module.py index 5036cf7b48..e250b4cfdd 100644 --- a/tests/integrations/redis/test_redis_cache_module.py +++ b/tests/integrations/redis/test_redis_cache_module.py @@ -83,21 +83,28 @@ def test_cache_basic(sentry_init, capture_events, capture_items, span_streaming) 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" + assert payloads[2]["attributes"][SPANDATA.CACHE_KEY] == ["mycachekey"] # 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" + assert payloads[4]["attributes"][SPANDATA.CACHE_KEY] == ["mycachekey1"] # 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" + assert payloads[6]["attributes"][SPANDATA.CACHE_KEY] == ["mycachekey2"] # 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[8]["attributes"][SPANDATA.CACHE_KEY] == [ + "mycachekey1", + "mycachekey2", + ] assert payloads[9]["name"] == "custom parent" else: @@ -169,12 +176,14 @@ def test_cache_keys(sentry_init, capture_events, capture_items, span_streaming): assert payloads[1]["name"] == "GET 'blub'" assert payloads[2]["attributes"]["sentry.op"] == "cache.get" assert payloads[2]["name"] == "blub" + assert payloads[2]["attributes"][SPANDATA.CACHE_KEY] == ["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" + assert payloads[4]["attributes"][SPANDATA.CACHE_KEY] == ["blubkeything"] # bl: db only (no prefix match) assert payloads[5]["attributes"]["sentry.op"] == "db.redis" diff --git a/tests/integrations/redis/test_redis_cache_module_async.py b/tests/integrations/redis/test_redis_cache_module_async.py index 02cda07c34..9ea86662b3 100644 --- a/tests/integrations/redis/test_redis_cache_module_async.py +++ b/tests/integrations/redis/test_redis_cache_module_async.py @@ -15,6 +15,7 @@ ) import sentry_sdk +from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.utils import parse_version @@ -83,6 +84,7 @@ async def test_cache_basic(sentry_init, capture_events, capture_items, span_stre assert parent_span["name"] == "custom parent" assert db_span["attributes"]["sentry.op"] == "db.redis" assert cache_span["attributes"]["sentry.op"] == "cache.get" + assert cache_span["attributes"][SPANDATA.CACHE_KEY] == ["myasynccachekey"] else: events = capture_events() with sentry_sdk.start_transaction(): @@ -132,12 +134,14 @@ async def test_cache_keys(sentry_init, capture_events, capture_items, span_strea assert payloads[1]["name"] == "GET 'ablub'" assert payloads[2]["attributes"]["sentry.op"] == "cache.get" assert payloads[2]["name"] == "ablub" + assert payloads[2]["attributes"][SPANDATA.CACHE_KEY] == ["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" + assert payloads[4]["attributes"][SPANDATA.CACHE_KEY] == ["ablubkeything"] # abl: db only (no prefix match) assert payloads[5]["attributes"]["sentry.op"] == "db.redis"