Skip to content
Merged
35 changes: 32 additions & 3 deletions sentry_sdk/integrations/redis/_async_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
from sentry_sdk.integrations.redis.consts import (
SPAN_ORIGIN,
)
from sentry_sdk.integrations.redis.modules.caches import (
_compile_cache_span_properties,
_set_cache_data,
)
from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
from sentry_sdk.integrations.redis.utils import (
_extract_key,
_get_safe_command,
_set_client_data,
_set_pipeline_data,
Expand Down Expand Up @@ -42,6 +45,16 @@ async def _sentry_execute(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
if client.get_integration(RedisIntegration) is None:
return await old_execute(self, *args, **kwargs)

sentry_sdk.add_breadcrumb(
message="redis.pipeline.execute",
type="redis",
category="redis",
data={
"redis.is_cluster": is_cluster,
"redis.transaction": False if is_cluster else self.is_transaction,
},
)

span_streaming = has_span_streaming_enabled(client.options)

span: "Union[Span, StreamedSpan]"
Expand Down Expand Up @@ -103,6 +116,24 @@ async def _sentry_execute_command(
if integration is None:
return await old_execute_command(self, name, *args, **kwargs)

db_properties = _compile_db_span_properties(integration, name, args)

breadcrumb_data = {
"redis.is_cluster": is_cluster,
"redis.command": name,
"db.operation": name,
}
key = _extract_key(name, args)
if key is not None:
breadcrumb_data["redis.key"] = key

sentry_sdk.add_breadcrumb(
message=db_properties["description"],
type="redis",
category="redis",
data=breadcrumb_data,
)

span_streaming = has_span_streaming_enabled(client.options)

if span_streaming and sentry_sdk.traces.get_current_span() is None:
Expand Down Expand Up @@ -140,8 +171,6 @@ async def _sentry_execute_command(
)
cache_span.__enter__()

db_properties = _compile_db_span_properties(integration, name, args)

additional_db_span_attributes = {}
with capture_internal_exceptions():
additional_db_span_attributes[SPANDATA.DB_QUERY_TEXT] = _get_safe_command(
Expand Down
36 changes: 32 additions & 4 deletions sentry_sdk/integrations/redis/_sync_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
from sentry_sdk.integrations.redis.consts import (
SPAN_ORIGIN,
)
from sentry_sdk.integrations.redis.modules.caches import (
_compile_cache_span_properties,
_set_cache_data,
)
from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
from sentry_sdk.integrations.redis.utils import (
_extract_key,
_get_safe_command,
_set_client_data,
_set_pipeline_data,
Expand Down Expand Up @@ -39,8 +42,17 @@ def sentry_patched_execute(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
if client.get_integration(RedisIntegration) is None:
return old_execute(self, *args, **kwargs)

span_streaming = has_span_streaming_enabled(client.options)
sentry_sdk.add_breadcrumb(
message="redis.pipeline.execute",
type="redis",
category="redis",
data={
"redis.is_cluster": is_cluster,
"redis.transaction": False if is_cluster else self.transaction,
},
)

span_streaming = has_span_streaming_enabled(client.options)
span: "Union[Span, StreamedSpan]"
if span_streaming:
if sentry_sdk.traces.get_current_span() is None:
Expand Down Expand Up @@ -102,6 +114,24 @@ def sentry_patched_execute_command(
if integration is None:
return old_execute_command(self, name, *args, **kwargs)

db_properties = _compile_db_span_properties(integration, name, args)

breadcrumb_data = {
"redis.is_cluster": is_cluster,
"redis.command": name,
"db.operation": name,
}
key = _extract_key(name, args)
if key is not None:
breadcrumb_data["redis.key"] = key

sentry_sdk.add_breadcrumb(
message=db_properties["description"],
type="redis",
category="redis",
data=breadcrumb_data,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breadcrumb callbacks can block Redis operations

Medium Severity

The new add_breadcrumb calls run outside capture_internal_exceptions. If before_breadcrumb raises, the exception escapes the wrapper before the underlying Redis command executes, turning telemetry callback failures into application failures.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7472af9. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point. Will take care of it in #7133


span_streaming = has_span_streaming_enabled(client.options)

if span_streaming and sentry_sdk.traces.get_current_span() is None:
Expand Down Expand Up @@ -139,8 +169,6 @@ def sentry_patched_execute_command(
)
cache_span.__enter__()

db_properties = _compile_db_span_properties(integration, name, args)

additional_db_span_attributes = {}
with capture_internal_exceptions():
additional_db_span_attributes[SPANDATA.DB_QUERY_TEXT] = _get_safe_command(
Expand Down
28 changes: 19 additions & 9 deletions sentry_sdk/integrations/redis/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,22 @@ def _set_client_data(
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
):
if isinstance(span, StreamedSpan):
span.set_attribute("db.redis.key", args[0])
else:
span.set_tag("redis.key", args[0])
key = _extract_key(name, args)
if key is not None:
if isinstance(span, StreamedSpan):
span.set_attribute("db.redis.key", key)
else:
span.set_tag("redis.key", key)


def _extract_key(name: str, args: "Any") -> "Optional[str]":
if not name or not args:
return None

name_low = name.lower()
if (name_low in _SINGLE_KEY_COMMANDS) or (
name_low in _MULTI_KEY_COMMANDS and len(args) == 1
):
return args[0]

return None
7 changes: 1 addition & 6 deletions sentry_sdk/tracing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,7 @@ def record_sql_queries(
def maybe_create_breadcrumbs_from_span(
scope: "sentry_sdk.Scope", span: "sentry_sdk.tracing.Span"
) -> None:
if span.op == OP.DB_REDIS:
scope.add_breadcrumb(
message=span.description, type="redis", category="redis", data=span._tags
)

elif span.op == OP.HTTP_CLIENT:
if span.op == OP.HTTP_CLIENT:
level = None
status_code = span._data.get(SPANDATA.HTTP_STATUS_CODE)
if status_code:
Expand Down
Loading