feat(redis): Support streaming spans #6083
5 issues
High
NameError: Span is only imported under TYPE_CHECKING but used at runtime - `sentry_sdk/integrations/redis/utils.py:115`
The Span class is imported only under TYPE_CHECKING (line 17), which means it's not available at runtime. However, isinstance(span, Span) is called at line 115, which will raise NameError: name 'Span' is not defined whenever _set_pipeline_data is called with any span type.
Missing exception handling in async Redis client causes span leak on error - `sentry_sdk/integrations/redis/_async_common.py:145-147`
In _sentry_execute_command, if old_execute_command raises an exception, the manually-entered db_span and cache_span are never exited via __exit__. The sync version properly wraps this in a try/finally block (lines 151-161 in _sync_common.py), but the async version does not. This leads to resource leaks and orphaned spans when Redis commands fail.
Also found at:
sentry_sdk/integrations/redis/_async_common.py:147sentry_sdk/integrations/redis/_sync_common.py:158
NameError at runtime: 'Span' is not defined in isinstance check - `sentry_sdk/integrations/redis/utils.py:115-117`
The Span class is only imported under TYPE_CHECKING (line 17), but it's used in an isinstance(span, Span) check at runtime (line 115). Since TYPE_CHECKING is False at runtime, Span will not be defined, causing a NameError when _set_pipeline_data is called. This will crash Redis pipeline operations.
Medium
Span resources leak when Redis command execution raises an exception - `sentry_sdk/integrations/redis/_async_common.py:145-156`
The _sentry_execute_command function manually calls __enter__() and __exit__() on spans (lines 126, 145, 152, 156) without try/finally protection. If await old_execute_command() on line 150 raises an exception, neither db_span.__exit__() nor cache_span.__exit__() will be called. This causes spans to never be finished/ended, leading to resource leaks and orphaned spans in the tracing system. Additionally, the span status won't be set to ERROR since __exit__ is called with (None, None, None) even in successful cases.
Unbound variable 'value' used in finally block when exception occurs - `sentry_sdk/integrations/redis/_sync_common.py:158`
When old_execute_command raises an exception, the value variable is never assigned. The finally block then attempts to use value in _set_cache_data(cache_span, self, cache_properties, value), which will raise a NameError. While capture_internal_exceptions() will catch this error silently, it prevents proper cache span data from being set and masks the actual exception behavior. The variable should be initialized to None before the try block.
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| code-review | 3 | 1m 34s | $1.84 |
| find-bugs | 2 | 3m 25s | $2.81 |
| skill-scanner | 0 | 1m 32s | $0.52 |
| security-review | 0 | 3m 19s | $0.78 |
Duration: 9m 51s · Tokens: 3.6M in / 40.7k out · Cost: $5.99 (+extraction: $0.01, +merge: $0.00, +fix_gate: $0.01, +dedup: $0.00)