@@ -212,6 +212,110 @@ async def patched_async_pipeline_immediate_execute(pipeline_self, *args, **kwarg
212212 except ImportError :
213213 logger .debug ("redis.asyncio not available" )
214214
215+ # Patch RedisCluster.execute_command (separate class, NOT a subclass of Redis)
216+ try :
217+ from redis .cluster import ClusterPipeline as SyncClusterPipeline
218+ from redis .cluster import RedisCluster
219+
220+ if hasattr (RedisCluster , "execute_command" ):
221+ original_cluster_execute = RedisCluster .execute_command
222+ instrumentation = self
223+
224+ def patched_cluster_execute_command (cluster_self , * args , ** kwargs ):
225+ """Patched RedisCluster.execute_command method."""
226+ sdk = TuskDrift .get_instance ()
227+
228+ if sdk .mode == TuskDriftMode .DISABLED :
229+ return original_cluster_execute (cluster_self , * args , ** kwargs )
230+
231+ return instrumentation ._traced_execute_command (
232+ cluster_self ,
233+ original_cluster_execute ,
234+ sdk ,
235+ args ,
236+ kwargs ,
237+ )
238+
239+ RedisCluster .execute_command = patched_cluster_execute_command
240+ logger .debug ("redis.cluster.RedisCluster.execute_command instrumented" )
241+
242+ # Patch ClusterPipeline.execute
243+ if hasattr (SyncClusterPipeline , "execute" ):
244+ original_cluster_pipeline_execute = SyncClusterPipeline .execute
245+ instrumentation = self
246+
247+ def patched_cluster_pipeline_execute (pipeline_self , * args , ** kwargs ):
248+ """Patched ClusterPipeline.execute method."""
249+ sdk = TuskDrift .get_instance ()
250+
251+ if sdk .mode == TuskDriftMode .DISABLED :
252+ return original_cluster_pipeline_execute (pipeline_self , * args , ** kwargs )
253+
254+ return instrumentation ._traced_cluster_pipeline_execute (
255+ pipeline_self ,
256+ original_cluster_pipeline_execute ,
257+ sdk ,
258+ args ,
259+ kwargs ,
260+ )
261+
262+ SyncClusterPipeline .execute = patched_cluster_pipeline_execute
263+ logger .debug ("redis.cluster.ClusterPipeline.execute instrumented" )
264+ except ImportError :
265+ logger .debug ("redis.cluster not available" )
266+
267+ # Patch async RedisCluster.execute_command
268+ try :
269+ from redis .asyncio .cluster import ClusterPipeline as AsyncClusterPipeline
270+ from redis .asyncio .cluster import RedisCluster as AsyncRedisCluster
271+
272+ if hasattr (AsyncRedisCluster , "execute_command" ):
273+ original_async_cluster_execute = AsyncRedisCluster .execute_command
274+ instrumentation = self
275+
276+ async def patched_async_cluster_execute_command (cluster_self , * args , ** kwargs ):
277+ """Patched async RedisCluster.execute_command method."""
278+ sdk = TuskDrift .get_instance ()
279+
280+ if sdk .mode == TuskDriftMode .DISABLED :
281+ return await original_async_cluster_execute (cluster_self , * args , ** kwargs )
282+
283+ return await instrumentation ._traced_async_execute_command (
284+ cluster_self ,
285+ original_async_cluster_execute ,
286+ sdk ,
287+ args ,
288+ kwargs ,
289+ )
290+
291+ AsyncRedisCluster .execute_command = patched_async_cluster_execute_command
292+ logger .debug ("redis.asyncio.cluster.RedisCluster.execute_command instrumented" )
293+
294+ # Patch async ClusterPipeline.execute
295+ if hasattr (AsyncClusterPipeline , "execute" ):
296+ original_async_cluster_pipeline_execute = AsyncClusterPipeline .execute
297+ instrumentation = self
298+
299+ async def patched_async_cluster_pipeline_execute (pipeline_self , * args , ** kwargs ):
300+ """Patched async ClusterPipeline.execute method."""
301+ sdk = TuskDrift .get_instance ()
302+
303+ if sdk .mode == TuskDriftMode .DISABLED :
304+ return await original_async_cluster_pipeline_execute (pipeline_self , * args , ** kwargs )
305+
306+ return await instrumentation ._traced_async_cluster_pipeline_execute (
307+ pipeline_self ,
308+ original_async_cluster_pipeline_execute ,
309+ sdk ,
310+ args ,
311+ kwargs ,
312+ )
313+
314+ AsyncClusterPipeline .execute = patched_async_cluster_pipeline_execute
315+ logger .debug ("redis.asyncio.cluster.ClusterPipeline.execute instrumented" )
316+ except ImportError :
317+ logger .debug ("redis.asyncio.cluster not available" )
318+
215319 def _traced_execute_command (
216320 self , redis_client : Any , original_execute : Any , sdk : TuskDrift , args : tuple , kwargs : dict
217321 ) -> Any :
@@ -519,6 +623,59 @@ async def _traced_async_pipeline_execute(
519623 pipeline , original_execute , sdk , args , kwargs , command_str , command_stack
520624 )
521625
626+ def _traced_cluster_pipeline_execute (
627+ self , pipeline : Any , original_execute : Any , sdk : TuskDrift , args : tuple , kwargs : dict
628+ ) -> Any :
629+ """Traced ClusterPipeline.execute method.
630+
631+ Must snapshot the command queue before calling original_execute because
632+ ClusterPipeline.execute resets the queue in its finally block.
633+ """
634+ if sdk .mode == TuskDriftMode .DISABLED :
635+ return original_execute (pipeline , * args , ** kwargs )
636+
637+ command_stack = list (self ._get_pipeline_commands (pipeline ))
638+ command_str = self ._format_pipeline_commands (command_stack )
639+
640+ def original_call ():
641+ return original_execute (pipeline , * args , ** kwargs )
642+
643+ if sdk .mode == TuskDriftMode .REPLAY :
644+ return handle_replay_mode (
645+ replay_mode_handler = lambda : self ._replay_pipeline_execute (sdk , command_str , command_stack ),
646+ no_op_request_handler = lambda : [],
647+ is_server_request = False ,
648+ )
649+
650+ return handle_record_mode (
651+ original_function_call = original_call ,
652+ record_mode_handler = lambda is_pre_app_start : self ._record_pipeline_execute (
653+ pipeline , original_execute , sdk , args , kwargs , command_str , command_stack , is_pre_app_start
654+ ),
655+ span_kind = OTelSpanKind .CLIENT ,
656+ )
657+
658+ async def _traced_async_cluster_pipeline_execute (
659+ self , pipeline : Any , original_execute : Any , sdk : TuskDrift , args : tuple , kwargs : dict
660+ ) -> Any :
661+ """Traced async ClusterPipeline.execute method."""
662+ if sdk .mode == TuskDriftMode .DISABLED :
663+ return await original_execute (pipeline , * args , ** kwargs )
664+
665+ command_stack = list (self ._get_pipeline_commands (pipeline ))
666+ command_str = self ._format_pipeline_commands (command_stack )
667+
668+ if sdk .mode == TuskDriftMode .REPLAY :
669+ return handle_replay_mode (
670+ replay_mode_handler = lambda : self ._replay_pipeline_execute (sdk , command_str , command_stack ),
671+ no_op_request_handler = lambda : [],
672+ is_server_request = False ,
673+ )
674+
675+ return await self ._record_async_pipeline_execute (
676+ pipeline , original_execute , sdk , args , kwargs , command_str , command_stack
677+ )
678+
522679 async def _record_async_pipeline_execute (
523680 self ,
524681 pipeline : Any ,
@@ -699,6 +856,16 @@ def _get_pipeline_commands(self, pipeline: Any) -> list:
699856 return pipeline .command_stack
700857 elif hasattr (pipeline , "_command_stack" ):
701858 return pipeline ._command_stack
859+ # ClusterPipeline stores commands in _execution_strategy._command_queue
860+ elif hasattr (pipeline , "_execution_strategy" ):
861+ strategy = pipeline ._execution_strategy
862+ if hasattr (strategy , "_command_queue" ):
863+ return strategy ._command_queue
864+ elif hasattr (strategy , "command_queue" ):
865+ return strategy .command_queue
866+ # Async ClusterPipeline stores commands in _command_queue directly
867+ elif hasattr (pipeline , "_command_queue" ):
868+ return pipeline ._command_queue
702869 except AttributeError :
703870 pass
704871 return []
0 commit comments