1515"""
1616Instrument `redis`_ to report Redis queries.
1717
18- There are two options for instrumenting code. The first option is to use the
19- ``opentelemetry-instrument`` executable which will automatically
20- instrument your Redis client. The second is to programmatically enable
21- instrumentation via the following code:
22-
2318.. _redis: https://pypi.org/project/redis/
2419
25- Usage
26- -----
20+
21+ Instrument All Clients
22+ ----------------------
23+
24+ The easiest way to instrument all redis client instances is by
25+ ``RedisInstrumentor().instrument()``:
2726
2827.. code:: python
2928
3837 client = redis.StrictRedis(host="localhost", port=6379)
3938 client.get("my-key")
4039
41- Async Redis clients (i.e. redis.asyncio.Redis) are also instrumented in the same way:
40+ Async Redis clients (i.e. `` redis.asyncio.Redis`` ) are also instrumented in the same way:
4241
4342.. code:: python
4443
@@ -54,19 +53,44 @@ async def redis_get():
5453 client = redis.asyncio.Redis(host="localhost", port=6379)
5554 await client.get("my-key")
5655
57- The `instrument` method accepts the following keyword args:
56+ .. note::
57+ Calling the ``instrument`` method will instrument the client classes, so any client
58+ created after the ``instrument`` call will be instrumented. To instrument only a
59+ single client, use :func:`RedisInstrumentor.instrument_client` method.
5860
59- tracer_provider (TracerProvider) - an optional tracer provider
61+ Instrument Single Client
62+ ------------------------
6063
61- request_hook (Callable) - a function with extra user-defined logic to be performed before performing the request
62- this function signature is: def request_hook(span: Span, instance: redis.connection.Connection, args, kwargs) -> None
64+ The :func:`RedisInstrumentor.instrument_client` can instrument a connection instance. This is useful when there are multiple clients with a different redis database index.
65+ Or, you might have a different connection pool used for an application function you
66+ don't want instrumented.
6367
64- response_hook (Callable) - a function with extra user-defined logic to be performed after performing the request
65- this function signature is: def response_hook(span: Span, instance: redis.connection.Connection, response) -> None
68+ .. code:: python
6669
67- for example:
70+ from opentelemetry.instrumentation.redis import RedisInstrumentor
71+ import redis
72+
73+ instrumented_client = redis.Redis()
74+ not_instrumented_client = redis.Redis()
75+
76+ # Instrument redis
77+ RedisInstrumentor.instrument_client(client=instrumented_client)
78+
79+ # This will report a span with the default settings
80+ instrumented_client.get("my-key")
6881
69- .. code: python
82+ # This will not have a span
83+ not_instrumented_client.get("my-key")
84+
85+ .. warning::
86+ All client instances created after calling ``RedisInstrumentor().instrument`` will
87+ be instrumented. To avoid instrumenting all clients, use
88+ :func:`RedisInstrumentor.instrument_client` .
89+
90+ Request/Response Hooks
91+ ----------------------
92+
93+ .. code:: python
7094
7195 from opentelemetry.instrumentation.redis import RedisInstrumentor
7296 import redis
@@ -86,7 +110,6 @@ def response_hook(span, instance, response):
86110 client = redis.StrictRedis(host="localhost", port=6379)
87111 client.get("my-key")
88112
89-
90113API
91114---
92115"""
@@ -110,16 +133,16 @@ def response_hook(span, instance, response):
110133from opentelemetry .instrumentation .redis .version import __version__
111134from opentelemetry .instrumentation .utils import unwrap
112135from opentelemetry .semconv .trace import SpanAttributes
113- from opentelemetry .trace import Span , StatusCode
136+ from opentelemetry .trace import Span , StatusCode , TracerProvider , get_tracer
114137
115138_DEFAULT_SERVICE = "redis"
116139
117- _RequestHookT = typing .Optional [
140+ RequestHook = typing .Optional [
118141 typing .Callable [
119- [Span , redis .connection .Connection , typing .List , typing .Dict ], None
142+ [Span , redis .connection .Connection , typing .Tuple , typing .Dict ], None
120143 ]
121144]
122- _ResponseHookT = typing .Optional [
145+ ResponseHook = typing .Optional [
123146 typing .Callable [[Span , redis .connection .Connection , Any ], None ]
124147]
125148_logger = logging .getLogger (__name__ )
@@ -252,8 +275,8 @@ def _build_span_meta_data_for_pipeline(instance):
252275
253276def _traced_execute_factory (
254277 tracer ,
255- request_hook : _RequestHookT = None ,
256- response_hook : _ResponseHookT = None ,
278+ request_hook : RequestHook = None ,
279+ response_hook : ResponseHook = None ,
257280):
258281 def _traced_execute_command (func , instance , args , kwargs ):
259282 query = _format_command_args (args )
@@ -282,8 +305,8 @@ def _traced_execute_command(func, instance, args, kwargs):
282305
283306def _traced_execute_pipeline_factory (
284307 tracer ,
285- request_hook : _RequestHookT = None ,
286- response_hook : _ResponseHookT = None ,
308+ request_hook : RequestHook = None ,
309+ response_hook : ResponseHook = None ,
287310):
288311 def _traced_execute_pipeline (func , instance , args , kwargs ):
289312 (
@@ -322,8 +345,8 @@ def _traced_execute_pipeline(func, instance, args, kwargs):
322345
323346def _async_traced_execute_factory (
324347 tracer ,
325- request_hook : _RequestHookT = None ,
326- response_hook : _ResponseHookT = None ,
348+ request_hook : RequestHook = None ,
349+ response_hook : ResponseHook = None ,
327350):
328351 async def _async_traced_execute_command (func , instance , args , kwargs ):
329352 query = _format_command_args (args )
@@ -348,8 +371,8 @@ async def _async_traced_execute_command(func, instance, args, kwargs):
348371
349372def _async_traced_execute_pipeline_factory (
350373 tracer ,
351- request_hook : _RequestHookT = None ,
352- response_hook : _ResponseHookT = None ,
374+ request_hook : RequestHook = None ,
375+ response_hook : ResponseHook = None ,
353376):
354377 async def _async_traced_execute_pipeline (func , instance , args , kwargs ):
355378 (
@@ -391,8 +414,8 @@ async def _async_traced_execute_pipeline(func, instance, args, kwargs):
391414# pylint: disable=R0915
392415def _instrument (
393416 tracer ,
394- request_hook : _RequestHookT = None ,
395- response_hook : _ResponseHookT = None ,
417+ request_hook : RequestHook = None ,
418+ response_hook : ResponseHook = None ,
396419):
397420 _traced_execute_command = _traced_execute_factory (
398421 tracer , request_hook , response_hook
@@ -463,11 +486,11 @@ def _instrument(
463486 )
464487
465488
466- def _instrument_connection (
489+ def _instrument_client (
467490 client ,
468491 tracer ,
469- request_hook : _RequestHookT = None ,
470- response_hook : _ResponseHookT = None ,
492+ request_hook : RequestHook = None ,
493+ response_hook : ResponseHook = None ,
471494):
472495 # first, handle async clients and cluster clients
473496 _async_traced_execute = _async_traced_execute_factory (
@@ -539,22 +562,48 @@ def _pipeline_wrapper(func, instance, args, kwargs):
539562
540563
541564class RedisInstrumentor (BaseInstrumentor ):
542- """An instrumentor for Redis
543- See `BaseInstrumentor`
544- """
545-
546565 @staticmethod
547566 def _get_tracer (** kwargs ):
548567 tracer_provider = kwargs .get ("tracer_provider" )
549- return trace . get_tracer (
568+ return get_tracer (
550569 __name__ ,
551570 __version__ ,
552571 tracer_provider = tracer_provider ,
553572 schema_url = "https://opentelemetry.io/schemas/1.11.0" ,
554573 )
555574
556- def instrumentation_dependencies (self ) -> Collection [str ]:
557- return _instruments
575+ def instrument (
576+ self ,
577+ tracer_provider : typing .Optional [TracerProvider ] = None ,
578+ request_hook : RequestHook = None ,
579+ response_hook : ResponseHook = None ,
580+ ** kwargs ,
581+ ):
582+ """Instruments all Redis/StrictRedis/RedisCluster and async client instances.
583+
584+ Args:
585+ tracer_provider: A TracerProvider, defaults to global.
586+ request_hook:
587+ a function with extra user-defined logic to run before performing the request.
588+
589+ The ``args`` is a tuple, where items are
590+ command arguments. For example ``client.set("mykey", "value", ex=5)`` would
591+ have ``args`` as ``('SET', 'mykey', 'value', 'EX', 5)``.
592+
593+ The ``kwargs`` represents occasional ``options`` passed by redis. For example,
594+ if you use ``client.set("mykey", "value", get=True)``, the ``kwargs`` would be
595+ ``{'get': True}``.
596+ response_hook:
597+ a function with extra user-defined logic to run after the request is complete.
598+
599+ The ``args`` represents the response.
600+ """
601+ super ().instrument (
602+ tracer_provider = tracer_provider ,
603+ request_hook = request_hook ,
604+ response_hook = response_hook ,
605+ ** kwargs ,
606+ )
558607
559608 def _instrument (self , ** kwargs ):
560609 """Instruments the redis module
@@ -601,13 +650,44 @@ def _uninstrument(self, **kwargs):
601650 unwrap (redis .asyncio .cluster .ClusterPipeline , "execute" )
602651
603652 @staticmethod
604- def instrument_connection (
605- client , tracer_provider : None , request_hook = None , response_hook = None
653+ def instrument_client (
654+ client : typing .Union [
655+ redis .StrictRedis ,
656+ redis .Redis ,
657+ redis .asyncio .Redis ,
658+ redis .cluster .RedisCluster ,
659+ redis .asyncio .cluster .RedisCluster ,
660+ ],
661+ tracer_provider : typing .Optional [TracerProvider ] = None ,
662+ request_hook : RequestHook = None ,
663+ response_hook : ResponseHook = None ,
606664 ):
665+ """Instrument the provided Redis Client. The client can be sync or async.
666+ Cluster client is also supported.
667+
668+ Args:
669+ client: The redis client.
670+ tracer_provider: A TracerProvider, defaults to global.
671+ request_hook: a function with extra user-defined logic to run before
672+ performing the request.
673+
674+ The ``args`` is a tuple, where items are
675+ command arguments. For example ``client.set("mykey", "value", ex=5)`` would
676+ have ``args`` as ``('SET', 'mykey', 'value', 'EX', 5)``.
677+
678+ The ``kwargs`` represents occasional ``options`` passed by redis. For example,
679+ if you use ``client.set("mykey", "value", get=True)``, the ``kwargs`` would be
680+ ``{'get': True}``.
681+
682+ response_hook: a function with extra user-defined logic to run after
683+ the request is complete.
684+
685+ The ``args`` represents the response.
686+ """
607687 if not hasattr (client , INSTRUMENTATION_ATTR ):
608688 setattr (client , INSTRUMENTATION_ATTR , False )
609689 if not getattr (client , INSTRUMENTATION_ATTR ):
610- _instrument_connection (
690+ _instrument_client (
611691 client ,
612692 RedisInstrumentor ._get_tracer (tracer_provider = tracer_provider ),
613693 request_hook = request_hook ,
@@ -620,7 +700,20 @@ def instrument_connection(
620700 )
621701
622702 @staticmethod
623- def uninstrument_connection (client ):
703+ def uninstrument_client (
704+ client : typing .Union [
705+ redis .StrictRedis ,
706+ redis .Redis ,
707+ redis .asyncio .Redis ,
708+ redis .cluster .RedisCluster ,
709+ redis .asyncio .cluster .RedisCluster ,
710+ ],
711+ ):
712+ """Disables instrumentation for the given client instance
713+
714+ Args:
715+ client: The redis client
716+ """
624717 if getattr (client , INSTRUMENTATION_ATTR ):
625718 # for all clients we need to unwrap execute_command and pipeline functions
626719 unwrap (client , "execute_command" )
@@ -634,3 +727,7 @@ def uninstrument_connection(client):
634727 "Attempting to un-instrument Redis connection that wasn't instrumented"
635728 )
636729 return
730+
731+ def instrumentation_dependencies (self ) -> Collection [str ]:
732+ """Return a list of python packages with versions that the will be instrumented."""
733+ return _instruments
0 commit comments