77from sentry_sdk ._types import MYPY
88
99if MYPY :
10- from typing import Any
10+ from typing import Any , Sequence
1111
1212_SINGLE_KEY_COMMANDS = frozenset (
1313 ["decr" , "decrby" , "get" , "incr" , "incrby" , "pttl" , "set" , "setex" , "setnx" , "ttl" ]
1414)
1515_MULTI_KEY_COMMANDS = frozenset (["del" , "touch" , "unlink" ])
1616
17+ #: Trim argument lists to this many values
18+ _MAX_NUM_ARGS = 10
19+
20+
21+ def patch_redis_pipeline (pipeline_cls , is_cluster , get_command_args_fn ):
22+ # type: (Any, bool, Any) -> None
23+ old_execute = pipeline_cls .execute
24+
25+ def sentry_patched_execute (self , * args , ** kwargs ):
26+ # type: (Any, *Any, **Any) -> Any
27+ hub = Hub .current
28+
29+ if hub .get_integration (RedisIntegration ) is None :
30+ return old_execute (self , * args , ** kwargs )
31+
32+ with hub .start_span (op = "redis" , description = "redis.pipeline.execute" ) as span :
33+ with capture_internal_exceptions ():
34+ span .set_tag ("redis.is_cluster" , is_cluster )
35+ transaction = self .transaction if not is_cluster else False
36+ span .set_tag ("redis.transaction" , transaction )
37+
38+ commands = []
39+ for i , arg in enumerate (self .command_stack ):
40+ if i > _MAX_NUM_ARGS :
41+ break
42+ command_args = []
43+ for j , command_arg in enumerate (get_command_args_fn (arg )):
44+ if j > 0 :
45+ command_arg = repr (command_arg )
46+ command_args .append (command_arg )
47+ commands .append (" " .join (command_args ))
48+
49+ span .set_data (
50+ "redis.commands" ,
51+ {"count" : len (self .command_stack ), "first_ten" : commands },
52+ )
53+
54+ return old_execute (self , * args , ** kwargs )
55+
56+ pipeline_cls .execute = sentry_patched_execute
57+
58+
59+ def _get_redis_command_args (command ):
60+ # type: (Any) -> Sequence[Any]
61+ return command [0 ]
62+
63+
64+ def _parse_rediscluster_command (command ):
65+ # type: (Any) -> Sequence[Any]
66+ return command .args
67+
1768
1869def _patch_rediscluster ():
1970 # type: () -> None
@@ -22,7 +73,7 @@ def _patch_rediscluster():
2273 except ImportError :
2374 return
2475
25- patch_redis_client (rediscluster .RedisCluster )
76+ patch_redis_client (rediscluster .RedisCluster , is_cluster = True )
2677
2778 # up to v1.3.6, __version__ attribute is a tuple
2879 # from v2.0.0, __version__ is a string and VERSION a tuple
@@ -31,7 +82,12 @@ def _patch_rediscluster():
3182 # StrictRedisCluster was introduced in v0.2.0 and removed in v2.0.0
3283 # https://github.com/Grokzen/redis-py-cluster/blob/master/docs/release-notes.rst
3384 if (0 , 2 , 0 ) < version < (2 , 0 , 0 ):
34- patch_redis_client (rediscluster .StrictRedisCluster )
85+ pipeline_cls = rediscluster .StrictClusterPipeline
86+ patch_redis_client (rediscluster .StrictRedisCluster , is_cluster = True )
87+ else :
88+ pipeline_cls = rediscluster .ClusterPipeline
89+
90+ patch_redis_pipeline (pipeline_cls , True , _parse_rediscluster_command )
3591
3692
3793class RedisIntegration (Integration ):
@@ -45,25 +101,32 @@ def setup_once():
45101 except ImportError :
46102 raise DidNotEnable ("Redis client not installed" )
47103
48- patch_redis_client (redis .StrictRedis )
104+ patch_redis_client (redis .StrictRedis , is_cluster = False )
105+ patch_redis_pipeline (redis .client .Pipeline , False , _get_redis_command_args )
106+ try :
107+ strict_pipeline = redis .client .StrictPipeline # type: ignore
108+ except AttributeError :
109+ pass
110+ else :
111+ patch_redis_pipeline (strict_pipeline , False , _get_redis_command_args )
49112
50113 try :
51114 import rb .clients # type: ignore
52115 except ImportError :
53116 pass
54117 else :
55- patch_redis_client (rb .clients .FanoutClient )
56- patch_redis_client (rb .clients .MappingClient )
57- patch_redis_client (rb .clients .RoutingClient )
118+ patch_redis_client (rb .clients .FanoutClient , is_cluster = False )
119+ patch_redis_client (rb .clients .MappingClient , is_cluster = False )
120+ patch_redis_client (rb .clients .RoutingClient , is_cluster = False )
58121
59122 try :
60123 _patch_rediscluster ()
61124 except Exception :
62125 logger .exception ("Error occurred while patching `rediscluster` library" )
63126
64127
65- def patch_redis_client (cls ):
66- # type: (Any) -> None
128+ def patch_redis_client (cls , is_cluster ):
129+ # type: (Any, bool ) -> None
67130 """
68131 This function can be used to instrument custom redis client classes or
69132 subclasses.
@@ -83,14 +146,15 @@ def sentry_patched_execute_command(self, name, *args, **kwargs):
83146 with capture_internal_exceptions ():
84147 description_parts = [name ]
85148 for i , arg in enumerate (args ):
86- if i > 10 :
149+ if i > _MAX_NUM_ARGS :
87150 break
88151
89152 description_parts .append (repr (arg ))
90153
91154 description = " " .join (description_parts )
92155
93156 with hub .start_span (op = "redis" , description = description ) as span :
157+ span .set_tag ("redis.is_cluster" , is_cluster )
94158 if name :
95159 span .set_tag ("redis.command" , name )
96160
0 commit comments