7
7
from sentry_sdk ._types import MYPY
8
8
9
9
if MYPY :
10
- from typing import Any
10
+ from typing import Any , Sequence
11
11
12
12
_SINGLE_KEY_COMMANDS = frozenset (
13
13
["decr" , "decrby" , "get" , "incr" , "incrby" , "pttl" , "set" , "setex" , "setnx" , "ttl" ]
14
14
)
15
15
_MULTI_KEY_COMMANDS = frozenset (["del" , "touch" , "unlink" ])
16
16
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
+
17
68
18
69
def _patch_rediscluster ():
19
70
# type: () -> None
@@ -22,7 +73,7 @@ def _patch_rediscluster():
22
73
except ImportError :
23
74
return
24
75
25
- patch_redis_client (rediscluster .RedisCluster )
76
+ patch_redis_client (rediscluster .RedisCluster , is_cluster = True )
26
77
27
78
# up to v1.3.6, __version__ attribute is a tuple
28
79
# from v2.0.0, __version__ is a string and VERSION a tuple
@@ -31,7 +82,12 @@ def _patch_rediscluster():
31
82
# StrictRedisCluster was introduced in v0.2.0 and removed in v2.0.0
32
83
# https://github.com/Grokzen/redis-py-cluster/blob/master/docs/release-notes.rst
33
84
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 )
35
91
36
92
37
93
class RedisIntegration (Integration ):
@@ -45,25 +101,32 @@ def setup_once():
45
101
except ImportError :
46
102
raise DidNotEnable ("Redis client not installed" )
47
103
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 )
49
112
50
113
try :
51
114
import rb .clients # type: ignore
52
115
except ImportError :
53
116
pass
54
117
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 )
58
121
59
122
try :
60
123
_patch_rediscluster ()
61
124
except Exception :
62
125
logger .exception ("Error occurred while patching `rediscluster` library" )
63
126
64
127
65
- def patch_redis_client (cls ):
66
- # type: (Any) -> None
128
+ def patch_redis_client (cls , is_cluster ):
129
+ # type: (Any, bool ) -> None
67
130
"""
68
131
This function can be used to instrument custom redis client classes or
69
132
subclasses.
@@ -83,14 +146,15 @@ def sentry_patched_execute_command(self, name, *args, **kwargs):
83
146
with capture_internal_exceptions ():
84
147
description_parts = [name ]
85
148
for i , arg in enumerate (args ):
86
- if i > 10 :
149
+ if i > _MAX_NUM_ARGS :
87
150
break
88
151
89
152
description_parts .append (repr (arg ))
90
153
91
154
description = " " .join (description_parts )
92
155
93
156
with hub .start_span (op = "redis" , description = description ) as span :
157
+ span .set_tag ("redis.is_cluster" , is_cluster )
94
158
if name :
95
159
span .set_tag ("redis.command" , name )
96
160
0 commit comments