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.
60+
61+ Instrument Single Client
62+ ------------------------
5863
59- tracer_provider (TracerProvider) - an optional tracer provider
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.
6067
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
68+ .. code:: python
69+
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")
6381
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
82+ # This will not have a span
83+ not_instrumented_client.get("my-key")
6684
67- for example:
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` .
6889
69- .. code: python
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"""
@@ -111,7 +134,13 @@ def response_hook(span, instance, response):
111134from opentelemetry .instrumentation .redis .version import __version__
112135from opentelemetry .instrumentation .utils import unwrap
113136from opentelemetry .semconv .trace import SpanAttributes
114- from opentelemetry .trace import Span , StatusCode , Tracer
137+ from opentelemetry .trace import (
138+ Span ,
139+ StatusCode ,
140+ Tracer ,
141+ TracerProvider ,
142+ get_tracer ,
143+ )
115144
116145if TYPE_CHECKING :
117146 from typing import Awaitable , TypeVar
@@ -122,10 +151,10 @@ def response_hook(span, instance, response):
122151 import redis .cluster
123152 import redis .connection
124153
125- _RequestHookT = Callable [
154+ RequestHook = Callable [
126155 [Span , redis .connection .Connection , list [Any ], dict [str , Any ]], None
127156 ]
128- _ResponseHookT = Callable [[Span , redis .connection .Connection , Any ], None ]
157+ ResponseHook = Callable [[Span , redis .connection .Connection , Any ], None ]
129158
130159 AsyncPipelineInstance = TypeVar (
131160 "AsyncPipelineInstance" ,
@@ -148,6 +177,7 @@ def response_hook(span, instance, response):
148177
149178_DEFAULT_SERVICE = "redis"
150179_logger = logging .getLogger (__name__ )
180+ assert hasattr (redis , "VERSION" )
151181
152182_REDIS_ASYNCIO_VERSION = (4 , 2 , 0 )
153183_REDIS_CLUSTER_VERSION = (4 , 1 , 0 )
@@ -281,9 +311,9 @@ def _build_span_meta_data_for_pipeline(
281311
282312
283313def _traced_execute_factory (
284- tracer ,
285- request_hook : _RequestHookT = None ,
286- response_hook : _ResponseHookT = None ,
314+ tracer : Tracer ,
315+ request_hook : RequestHook | None = None ,
316+ response_hook : ResponseHook | None = None ,
287317):
288318 def _traced_execute_command (
289319 func : Callable [..., R ],
@@ -316,9 +346,9 @@ def _traced_execute_command(
316346
317347
318348def _traced_execute_pipeline_factory (
319- tracer ,
320- request_hook : _RequestHookT = None ,
321- response_hook : _ResponseHookT = None ,
349+ tracer : Tracer ,
350+ request_hook : RequestHook | None = None ,
351+ response_hook : ResponseHook | None = None ,
322352):
323353 def _traced_execute_pipeline (
324354 func : Callable [..., R ],
@@ -361,9 +391,9 @@ def _traced_execute_pipeline(
361391
362392
363393def _async_traced_execute_factory (
364- tracer ,
365- request_hook : _RequestHookT = None ,
366- response_hook : _ResponseHookT = None ,
394+ tracer : Tracer ,
395+ request_hook : RequestHook | None = None ,
396+ response_hook : ResponseHook | None = None ,
367397):
368398 async def _async_traced_execute_command (
369399 func : Callable [..., Awaitable [R ]],
@@ -392,9 +422,9 @@ async def _async_traced_execute_command(
392422
393423
394424def _async_traced_execute_pipeline_factory (
395- tracer ,
396- request_hook : _RequestHookT = None ,
397- response_hook : _ResponseHookT = None ,
425+ tracer : Tracer ,
426+ request_hook : RequestHook | None = None ,
427+ response_hook : ResponseHook | None = None ,
398428):
399429 async def _async_traced_execute_pipeline (
400430 func : Callable [..., Awaitable [R ]],
@@ -441,8 +471,8 @@ async def _async_traced_execute_pipeline(
441471# pylint: disable=R0915
442472def _instrument (
443473 tracer : Tracer ,
444- request_hook : _RequestHookT | None = None ,
445- response_hook : _ResponseHookT | None = None ,
474+ request_hook : RequestHook | None = None ,
475+ response_hook : ResponseHook | None = None ,
446476):
447477 _traced_execute_command = _traced_execute_factory (
448478 tracer , request_hook , response_hook
@@ -513,11 +543,11 @@ def _instrument(
513543 )
514544
515545
516- def _instrument_connection (
546+ def _instrument_client (
517547 client ,
518- tracer ,
519- request_hook : _RequestHookT = None ,
520- response_hook : _ResponseHookT = None ,
548+ tracer : Tracer ,
549+ request_hook : RequestHook | None = None ,
550+ response_hook : ResponseHook | None = None ,
521551):
522552 # first, handle async clients and cluster clients
523553 _async_traced_execute = _async_traced_execute_factory (
@@ -589,23 +619,48 @@ def _pipeline_wrapper(func, instance, args, kwargs):
589619
590620
591621class RedisInstrumentor (BaseInstrumentor ):
592- """An instrumentor for Redis.
593-
594- See `BaseInstrumentor`
595- """
596-
597622 @staticmethod
598623 def _get_tracer (** kwargs ):
599624 tracer_provider = kwargs .get ("tracer_provider" )
600- return trace . get_tracer (
625+ return get_tracer (
601626 __name__ ,
602627 __version__ ,
603628 tracer_provider = tracer_provider ,
604629 schema_url = "https://opentelemetry.io/schemas/1.11.0" ,
605630 )
606631
607- def instrumentation_dependencies (self ) -> Collection [str ]:
608- return _instruments
632+ def instrument (
633+ self ,
634+ tracer_provider : TracerProvider | None = None ,
635+ request_hook : RequestHook | None = None ,
636+ response_hook : ResponseHook | None = None ,
637+ ** kwargs ,
638+ ):
639+ """Instruments all Redis/StrictRedis/RedisCluster and async client instances.
640+
641+ Args:
642+ tracer_provider: A TracerProvider, defaults to global.
643+ request_hook:
644+ a function with extra user-defined logic to run before performing the request.
645+
646+ The ``args`` is a tuple, where items are
647+ command arguments. For example ``client.set("mykey", "value", ex=5)`` would
648+ have ``args`` as ``('SET', 'mykey', 'value', 'EX', 5)``.
649+
650+ The ``kwargs`` represents occasional ``options`` passed by redis. For example,
651+ if you use ``client.set("mykey", "value", get=True)``, the ``kwargs`` would be
652+ ``{'get': True}``.
653+ response_hook:
654+ a function with extra user-defined logic to run after the request is complete.
655+
656+ The ``args`` represents the response.
657+ """
658+ super ().instrument (
659+ tracer_provider = tracer_provider ,
660+ request_hook = request_hook ,
661+ response_hook = response_hook ,
662+ ** kwargs ,
663+ )
609664
610665 def _instrument (self , ** kwargs : Any ):
611666 """Instruments the redis module
@@ -652,13 +707,42 @@ def _uninstrument(self, **kwargs: Any):
652707 unwrap (redis .asyncio .cluster .ClusterPipeline , "execute" )
653708
654709 @staticmethod
655- def instrument_connection (
656- client , tracer_provider : None , request_hook = None , response_hook = None
710+ def instrument_client (
711+ client : redis .StrictRedis
712+ | redis .Redis
713+ | redis .asyncio .Redis
714+ | redis .cluster .RedisCluster
715+ | redis .asyncio .cluster .RedisCluster ,
716+ tracer_provider : TracerProvider | None = None ,
717+ request_hook : RequestHook | None = None ,
718+ response_hook : ResponseHook | None = None ,
657719 ):
720+ """Instrument the provided Redis Client. The client can be sync or async.
721+ Cluster client is also supported.
722+
723+ Args:
724+ client: The redis client.
725+ tracer_provider: A TracerProvider, defaults to global.
726+ request_hook: a function with extra user-defined logic to run before
727+ performing the request.
728+
729+ The ``args`` is a tuple, where items are
730+ command arguments. For example ``client.set("mykey", "value", ex=5)`` would
731+ have ``args`` as ``('SET', 'mykey', 'value', 'EX', 5)``.
732+
733+ The ``kwargs`` represents occasional ``options`` passed by redis. For example,
734+ if you use ``client.set("mykey", "value", get=True)``, the ``kwargs`` would be
735+ ``{'get': True}``.
736+
737+ response_hook: a function with extra user-defined logic to run after
738+ the request is complete.
739+
740+ The ``args`` represents the response.
741+ """
658742 if not hasattr (client , INSTRUMENTATION_ATTR ):
659743 setattr (client , INSTRUMENTATION_ATTR , False )
660744 if not getattr (client , INSTRUMENTATION_ATTR ):
661- _instrument_connection (
745+ _instrument_client (
662746 client ,
663747 RedisInstrumentor ._get_tracer (tracer_provider = tracer_provider ),
664748 request_hook = request_hook ,
@@ -671,7 +755,18 @@ def instrument_connection(
671755 )
672756
673757 @staticmethod
674- def uninstrument_connection (client ):
758+ def uninstrument_client (
759+ client : redis .StrictRedis
760+ | redis .Redis
761+ | redis .asyncio .Redis
762+ | redis .cluster .RedisCluster
763+ | redis .asyncio .cluster .RedisCluster ,
764+ ):
765+ """Disables instrumentation for the given client instance
766+
767+ Args:
768+ client: The redis client
769+ """
675770 if getattr (client , INSTRUMENTATION_ATTR ):
676771 # for all clients we need to unwrap execute_command and pipeline functions
677772 unwrap (client , "execute_command" )
@@ -685,3 +780,7 @@ def uninstrument_connection(client):
685780 "Attempting to un-instrument Redis connection that wasn't instrumented"
686781 )
687782 return
783+
784+ def instrumentation_dependencies (self ) -> Collection [str ]:
785+ """Return a list of python packages with versions that the will be instrumented."""
786+ return _instruments
0 commit comments