13
13
)
14
14
15
15
if TYPE_CHECKING :
16
+ from collections .abc import Callable
16
17
from typing import Any , Dict , Sequence
18
+ from redis import Redis , RedisCluster
19
+ from redis .asyncio .cluster import (
20
+ RedisCluster as AsyncRedisCluster ,
21
+ ClusterPipeline as AsyncClusterPipeline ,
22
+ )
17
23
from sentry_sdk .tracing import Span
18
24
19
25
_SINGLE_KEY_COMMANDS = frozenset (
@@ -83,8 +89,7 @@ def _set_pipeline_data(
83
89
):
84
90
# type: (Span, bool, Any, bool, Sequence[Any]) -> None
85
91
span .set_tag ("redis.is_cluster" , is_cluster )
86
- transaction = is_transaction if not is_cluster else False
87
- span .set_tag ("redis.transaction" , transaction )
92
+ span .set_tag ("redis.transaction" , is_transaction )
88
93
89
94
commands = []
90
95
for i , arg in enumerate (command_stack ):
@@ -118,7 +123,7 @@ def _set_client_data(span, is_cluster, name, *args):
118
123
span .set_tag ("redis.key" , args [0 ])
119
124
120
125
121
- def _set_db_data (span , connection_params ):
126
+ def _set_db_data_on_span (span , connection_params ):
122
127
# type: (Span, Dict[str, Any]) -> None
123
128
span .set_data (SPANDATA .DB_SYSTEM , "redis" )
124
129
@@ -135,8 +140,43 @@ def _set_db_data(span, connection_params):
135
140
span .set_data (SPANDATA .SERVER_PORT , port )
136
141
137
142
138
- def patch_redis_pipeline (pipeline_cls , is_cluster , get_command_args_fn ):
139
- # type: (Any, bool, Any) -> None
143
+ def _set_db_data (span , redis_instance ):
144
+ # type: (Span, Redis[Any]) -> None
145
+ try :
146
+ _set_db_data_on_span (span , redis_instance .connection_pool .connection_kwargs )
147
+ except AttributeError :
148
+ pass # connections_kwargs may be missing in some cases
149
+
150
+
151
+ def _set_cluster_db_data (span , redis_cluster_instance ):
152
+ # type: (Span, RedisCluster[Any]) -> None
153
+ default_node = redis_cluster_instance .get_default_node ()
154
+ if default_node is not None :
155
+ _set_db_data_on_span (
156
+ span , {"host" : default_node .host , "port" : default_node .port }
157
+ )
158
+
159
+
160
+ def _set_async_cluster_db_data (span , async_redis_cluster_instance ):
161
+ # type: (Span, AsyncRedisCluster[Any]) -> None
162
+ default_node = async_redis_cluster_instance .get_default_node ()
163
+ if default_node is not None and default_node .connection_kwargs is not None :
164
+ _set_db_data_on_span (span , default_node .connection_kwargs )
165
+
166
+
167
+ def _set_async_cluster_pipeline_db_data (span , async_redis_cluster_pipeline_instance ):
168
+ # type: (Span, AsyncClusterPipeline[Any]) -> None
169
+ with capture_internal_exceptions ():
170
+ _set_async_cluster_db_data (
171
+ span ,
172
+ # the AsyncClusterPipeline has always had a `_client` attr but it is private so potentially problematic and mypy
173
+ # does not recognize it - see https://github.com/redis/redis-py/blame/v5.0.0/redis/asyncio/cluster.py#L1386
174
+ async_redis_cluster_pipeline_instance ._client , # type: ignore[attr-defined]
175
+ )
176
+
177
+
178
+ def patch_redis_pipeline (pipeline_cls , is_cluster , get_command_args_fn , set_db_data_fn ):
179
+ # type: (Any, bool, Any, Callable[[Span, Any], None]) -> None
140
180
old_execute = pipeline_cls .execute
141
181
142
182
def sentry_patched_execute (self , * args , ** kwargs ):
@@ -150,12 +190,12 @@ def sentry_patched_execute(self, *args, **kwargs):
150
190
op = OP .DB_REDIS , description = "redis.pipeline.execute"
151
191
) as span :
152
192
with capture_internal_exceptions ():
153
- _set_db_data (span , self . connection_pool . connection_kwargs )
193
+ set_db_data_fn (span , self )
154
194
_set_pipeline_data (
155
195
span ,
156
196
is_cluster ,
157
197
get_command_args_fn ,
158
- self .transaction ,
198
+ False if is_cluster else self .transaction ,
159
199
self .command_stack ,
160
200
)
161
201
@@ -164,8 +204,8 @@ def sentry_patched_execute(self, *args, **kwargs):
164
204
pipeline_cls .execute = sentry_patched_execute
165
205
166
206
167
- def patch_redis_client (cls , is_cluster ):
168
- # type: (Any, bool) -> None
207
+ def patch_redis_client (cls , is_cluster , set_db_data_fn ):
208
+ # type: (Any, bool, Callable[[Span, Any], None] ) -> None
169
209
"""
170
210
This function can be used to instrument custom redis client classes or
171
211
subclasses.
@@ -189,11 +229,7 @@ def sentry_patched_execute_command(self, name, *args, **kwargs):
189
229
description = description [: integration .max_data_size - len ("..." )] + "..."
190
230
191
231
with hub .start_span (op = OP .DB_REDIS , description = description ) as span :
192
- try :
193
- _set_db_data (span , self .connection_pool .connection_kwargs )
194
- except AttributeError :
195
- pass # connections_kwargs may be missing in some cases
196
-
232
+ set_db_data_fn (span , self )
197
233
_set_client_data (span , is_cluster , name , * args )
198
234
199
235
return old_execute_command (self , name , * args , ** kwargs )
@@ -203,14 +239,16 @@ def sentry_patched_execute_command(self, name, *args, **kwargs):
203
239
204
240
def _patch_redis (StrictRedis , client ): # noqa: N803
205
241
# type: (Any, Any) -> None
206
- patch_redis_client (StrictRedis , is_cluster = False )
207
- patch_redis_pipeline (client .Pipeline , False , _get_redis_command_args )
242
+ patch_redis_client (StrictRedis , is_cluster = False , set_db_data_fn = _set_db_data )
243
+ patch_redis_pipeline (client .Pipeline , False , _get_redis_command_args , _set_db_data )
208
244
try :
209
245
strict_pipeline = client .StrictPipeline
210
246
except AttributeError :
211
247
pass
212
248
else :
213
- patch_redis_pipeline (strict_pipeline , False , _get_redis_command_args )
249
+ patch_redis_pipeline (
250
+ strict_pipeline , False , _get_redis_command_args , _set_db_data
251
+ )
214
252
215
253
try :
216
254
import redis .asyncio
@@ -222,8 +260,56 @@ def _patch_redis(StrictRedis, client): # noqa: N803
222
260
patch_redis_async_pipeline ,
223
261
)
224
262
225
- patch_redis_async_client (redis .asyncio .client .StrictRedis )
226
- patch_redis_async_pipeline (redis .asyncio .client .Pipeline )
263
+ patch_redis_async_client (
264
+ redis .asyncio .client .StrictRedis ,
265
+ is_cluster = False ,
266
+ set_db_data_fn = _set_db_data ,
267
+ )
268
+ patch_redis_async_pipeline (
269
+ redis .asyncio .client .Pipeline ,
270
+ False ,
271
+ _get_redis_command_args ,
272
+ set_db_data_fn = _set_db_data ,
273
+ )
274
+
275
+
276
+ def _patch_redis_cluster ():
277
+ # type: () -> None
278
+ """Patches the cluster module on redis SDK (as opposed to rediscluster library)"""
279
+ try :
280
+ from redis import RedisCluster , cluster
281
+ except ImportError :
282
+ pass
283
+ else :
284
+ patch_redis_client (RedisCluster , True , _set_cluster_db_data )
285
+ patch_redis_pipeline (
286
+ cluster .ClusterPipeline ,
287
+ True ,
288
+ _parse_rediscluster_command ,
289
+ _set_cluster_db_data ,
290
+ )
291
+
292
+ try :
293
+ from redis .asyncio import cluster as async_cluster
294
+ except ImportError :
295
+ pass
296
+ else :
297
+ from sentry_sdk .integrations .redis .asyncio import (
298
+ patch_redis_async_client ,
299
+ patch_redis_async_pipeline ,
300
+ )
301
+
302
+ patch_redis_async_client (
303
+ async_cluster .RedisCluster ,
304
+ is_cluster = True ,
305
+ set_db_data_fn = _set_async_cluster_db_data ,
306
+ )
307
+ patch_redis_async_pipeline (
308
+ async_cluster .ClusterPipeline ,
309
+ True ,
310
+ _parse_rediscluster_command ,
311
+ set_db_data_fn = _set_async_cluster_pipeline_db_data ,
312
+ )
227
313
228
314
229
315
def _patch_rb ():
@@ -233,9 +319,15 @@ def _patch_rb():
233
319
except ImportError :
234
320
pass
235
321
else :
236
- patch_redis_client (rb .clients .FanoutClient , is_cluster = False )
237
- patch_redis_client (rb .clients .MappingClient , is_cluster = False )
238
- patch_redis_client (rb .clients .RoutingClient , is_cluster = False )
322
+ patch_redis_client (
323
+ rb .clients .FanoutClient , is_cluster = False , set_db_data_fn = _set_db_data
324
+ )
325
+ patch_redis_client (
326
+ rb .clients .MappingClient , is_cluster = False , set_db_data_fn = _set_db_data
327
+ )
328
+ patch_redis_client (
329
+ rb .clients .RoutingClient , is_cluster = False , set_db_data_fn = _set_db_data
330
+ )
239
331
240
332
241
333
def _patch_rediscluster ():
@@ -245,7 +337,9 @@ def _patch_rediscluster():
245
337
except ImportError :
246
338
return
247
339
248
- patch_redis_client (rediscluster .RedisCluster , is_cluster = True )
340
+ patch_redis_client (
341
+ rediscluster .RedisCluster , is_cluster = True , set_db_data_fn = _set_db_data
342
+ )
249
343
250
344
# up to v1.3.6, __version__ attribute is a tuple
251
345
# from v2.0.0, __version__ is a string and VERSION a tuple
@@ -255,11 +349,17 @@ def _patch_rediscluster():
255
349
# https://github.com/Grokzen/redis-py-cluster/blob/master/docs/release-notes.rst
256
350
if (0 , 2 , 0 ) < version < (2 , 0 , 0 ):
257
351
pipeline_cls = rediscluster .pipeline .StrictClusterPipeline
258
- patch_redis_client (rediscluster .StrictRedisCluster , is_cluster = True )
352
+ patch_redis_client (
353
+ rediscluster .StrictRedisCluster ,
354
+ is_cluster = True ,
355
+ set_db_data_fn = _set_db_data ,
356
+ )
259
357
else :
260
358
pipeline_cls = rediscluster .pipeline .ClusterPipeline
261
359
262
- patch_redis_pipeline (pipeline_cls , True , _parse_rediscluster_command )
360
+ patch_redis_pipeline (
361
+ pipeline_cls , True , _parse_rediscluster_command , set_db_data_fn = _set_db_data
362
+ )
263
363
264
364
265
365
class RedisIntegration (Integration ):
@@ -278,6 +378,7 @@ def setup_once():
278
378
raise DidNotEnable ("Redis client not installed" )
279
379
280
380
_patch_redis (StrictRedis , client )
381
+ _patch_redis_cluster ()
281
382
_patch_rb ()
282
383
283
384
try :
0 commit comments