2525
2626from opentelemetry import trace
2727from opentelemetry .instrumentation .redis import RedisInstrumentor
28+ from opentelemetry .instrumentation .utils import suppress_instrumentation
2829from opentelemetry .semconv ._incubating .attributes .db_attributes import (
2930 DB_REDIS_DATABASE_INDEX ,
3031 DB_SYSTEM ,
@@ -390,6 +391,7 @@ def redis_operations():
390391 self .assertEqual (span .kind , SpanKind .CLIENT )
391392 self .assertEqual (span .status .status_code , trace .StatusCode .UNSET )
392393
394+
393395 def test_span_name_empty_pipeline (self ):
394396 redis_client = fakeredis .FakeStrictRedis ()
395397 pipe = redis_client .pipeline ()
@@ -401,6 +403,75 @@ def test_span_name_empty_pipeline(self):
401403 self .assertEqual (spans [0 ].kind , SpanKind .CLIENT )
402404 self .assertEqual (spans [0 ].status .status_code , trace .StatusCode .UNSET )
403405
406+ def test_suppress_instrumentation_command (self ):
407+ redis_client = redis .Redis ()
408+
409+ with mock .patch .object (redis_client , "connection" ):
410+ # Execute command with suppression
411+ with suppress_instrumentation ():
412+ redis_client .get ("key" )
413+
414+ # No spans should be created
415+ spans = self .memory_exporter .get_finished_spans ()
416+ self .assertEqual (len (spans ), 0 )
417+
418+ # Verify that instrumentation works again after exiting the context
419+ with mock .patch .object (redis_client , "connection" ):
420+ redis_client .get ("key" )
421+
422+ spans = self .memory_exporter .get_finished_spans ()
423+ self .assertEqual (len (spans ), 1 )
424+
425+ def test_suppress_instrumentation_pipeline (self ):
426+ redis_client = fakeredis .FakeStrictRedis ()
427+
428+ with suppress_instrumentation ():
429+ pipe = redis_client .pipeline ()
430+ pipe .set ("key1" , "value1" )
431+ pipe .set ("key2" , "value2" )
432+ pipe .get ("key1" )
433+ pipe .execute ()
434+
435+ # No spans should be created
436+ spans = self .memory_exporter .get_finished_spans ()
437+ self .assertEqual (len (spans ), 0 )
438+
439+ # Verify that instrumentation works again after exiting the context
440+ pipe = redis_client .pipeline ()
441+ pipe .set ("key3" , "value3" )
442+ pipe .execute ()
443+
444+ spans = self .memory_exporter .get_finished_spans ()
445+ self .assertEqual (len (spans ), 1 )
446+ # Pipeline span could be "SET" or "redis.pipeline" depending on implementation
447+ self .assertIn (spans [0 ].name , ["SET" , "redis.pipeline" ])
448+
449+ def test_suppress_instrumentation_mixed (self ):
450+ redis_client = redis .Redis ()
451+
452+ # Regular instrumented call
453+ with mock .patch .object (redis_client , "connection" ):
454+ redis_client .set ("key1" , "value1" )
455+
456+ spans = self .memory_exporter .get_finished_spans ()
457+ self .assertEqual (len (spans ), 1 )
458+ self .memory_exporter .clear ()
459+
460+ # Suppressed call
461+ with suppress_instrumentation ():
462+ with mock .patch .object (redis_client , "connection" ):
463+ redis_client .set ("key2" , "value2" )
464+
465+ spans = self .memory_exporter .get_finished_spans ()
466+ self .assertEqual (len (spans ), 0 )
467+
468+ # Another regular instrumented call
469+ with mock .patch .object (redis_client , "connection" ):
470+ redis_client .get ("key1" )
471+
472+ spans = self .memory_exporter .get_finished_spans ()
473+ self .assertEqual (len (spans ), 1 )
474+
404475
405476class TestRedisAsync (TestBase , IsolatedAsyncioTestCase ):
406477 def assert_span_count (self , count : int ):
@@ -570,6 +641,70 @@ async def test_span_name_empty_pipeline(self):
570641 self .assertEqual (spans [0 ].status .status_code , trace .StatusCode .UNSET )
571642 self .instrumentor .uninstrument_client (client = redis_client )
572643
644+ @pytest .mark .asyncio
645+ async def test_suppress_instrumentation_async_command (self ):
646+ self .instrumentor .instrument (tracer_provider = self .tracer_provider )
647+ redis_client = FakeRedis ()
648+
649+ # Execute command with suppression
650+ with suppress_instrumentation ():
651+ await redis_client .get ("key" )
652+
653+ # No spans should be created
654+ self .assert_span_count (0 )
655+
656+ # Verify that instrumentation works again after exiting the context
657+ await redis_client .set ("key" , "value" )
658+ self .assert_span_count (1 )
659+ self .instrumentor .uninstrument ()
660+
661+ @pytest .mark .asyncio
662+ async def test_suppress_instrumentation_async_pipeline (self ):
663+ self .instrumentor .instrument (tracer_provider = self .tracer_provider )
664+ redis_client = FakeRedis ()
665+
666+ # Execute pipeline with suppression
667+ with suppress_instrumentation ():
668+ async with redis_client .pipeline () as pipe :
669+ await pipe .set ("key1" , "value1" )
670+ await pipe .set ("key2" , "value2" )
671+ await pipe .get ("key1" )
672+ await pipe .execute ()
673+
674+ # No spans should be created
675+ self .assert_span_count (0 )
676+
677+ # Verify that instrumentation works again after exiting the context
678+ async with redis_client .pipeline () as pipe :
679+ await pipe .set ("key3" , "value3" )
680+ await pipe .execute ()
681+
682+ spans = self .assert_span_count (1 )
683+ # Pipeline span could be "SET" or "redis.pipeline" depending on implementation
684+ self .assertIn (spans [0 ].name , ["SET" , "redis.pipeline" ])
685+ self .instrumentor .uninstrument ()
686+
687+ @pytest .mark .asyncio
688+ async def test_suppress_instrumentation_async_mixed (self ):
689+ self .instrumentor .instrument (tracer_provider = self .tracer_provider )
690+ redis_client = FakeRedis ()
691+
692+ # Regular instrumented call
693+ await redis_client .set ("key1" , "value1" )
694+ self .assert_span_count (1 )
695+ self .memory_exporter .clear ()
696+
697+ # Suppressed call
698+ with suppress_instrumentation ():
699+ await redis_client .set ("key2" , "value2" )
700+
701+ self .assert_span_count (0 )
702+
703+ # Another regular instrumented call
704+ await redis_client .get ("key1" )
705+ self .assert_span_count (1 )
706+ self .instrumentor .uninstrument ()
707+
573708
574709class TestRedisInstance (TestBase ):
575710 def assert_span_count (self , count : int ):
0 commit comments