15
15
"""
16
16
Instrument `redis`_ to report Redis queries.
17
17
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
-
23
18
.. _redis: https://pypi.org/project/redis/
24
19
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()``:
27
26
28
27
.. code:: python
29
28
38
37
client = redis.StrictRedis(host="localhost", port=6379)
39
38
client.get("my-key")
40
39
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:
42
41
43
42
.. code:: python
44
43
@@ -54,19 +53,44 @@ async def redis_get():
54
53
client = redis.asyncio.Redis(host="localhost", port=6379)
55
54
await client.get("my-key")
56
55
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
+ ------------------------
58
63
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.
60
67
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")
63
81
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")
66
84
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` .
68
89
69
- .. code: python
90
+ Request/Response Hooks
91
+ ----------------------
92
+
93
+ .. code:: python
70
94
71
95
from opentelemetry.instrumentation.redis import RedisInstrumentor
72
96
import redis
@@ -86,7 +110,6 @@ def response_hook(span, instance, response):
86
110
client = redis.StrictRedis(host="localhost", port=6379)
87
111
client.get("my-key")
88
112
89
-
90
113
API
91
114
---
92
115
"""
@@ -111,7 +134,13 @@ def response_hook(span, instance, response):
111
134
from opentelemetry .instrumentation .redis .version import __version__
112
135
from opentelemetry .instrumentation .utils import unwrap
113
136
from 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
+ )
115
144
116
145
if TYPE_CHECKING :
117
146
from typing import Awaitable , TypeVar
@@ -122,10 +151,10 @@ def response_hook(span, instance, response):
122
151
import redis .cluster
123
152
import redis .connection
124
153
125
- _RequestHookT = Callable [
154
+ RequestHook = Callable [
126
155
[Span , redis .connection .Connection , list [Any ], dict [str , Any ]], None
127
156
]
128
- _ResponseHookT = Callable [[Span , redis .connection .Connection , Any ], None ]
157
+ ResponseHook = Callable [[Span , redis .connection .Connection , Any ], None ]
129
158
130
159
AsyncPipelineInstance = TypeVar (
131
160
"AsyncPipelineInstance" ,
@@ -148,6 +177,7 @@ def response_hook(span, instance, response):
148
177
149
178
_DEFAULT_SERVICE = "redis"
150
179
_logger = logging .getLogger (__name__ )
180
+ assert hasattr (redis , "VERSION" )
151
181
152
182
_REDIS_ASYNCIO_VERSION = (4 , 2 , 0 )
153
183
_REDIS_CLUSTER_VERSION = (4 , 1 , 0 )
@@ -281,9 +311,9 @@ def _build_span_meta_data_for_pipeline(
281
311
282
312
283
313
def _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 ,
287
317
):
288
318
def _traced_execute_command (
289
319
func : Callable [..., R ],
@@ -316,9 +346,9 @@ def _traced_execute_command(
316
346
317
347
318
348
def _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 ,
322
352
):
323
353
def _traced_execute_pipeline (
324
354
func : Callable [..., R ],
@@ -361,9 +391,9 @@ def _traced_execute_pipeline(
361
391
362
392
363
393
def _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 ,
367
397
):
368
398
async def _async_traced_execute_command (
369
399
func : Callable [..., Awaitable [R ]],
@@ -392,9 +422,9 @@ async def _async_traced_execute_command(
392
422
393
423
394
424
def _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 ,
398
428
):
399
429
async def _async_traced_execute_pipeline (
400
430
func : Callable [..., Awaitable [R ]],
@@ -441,8 +471,8 @@ async def _async_traced_execute_pipeline(
441
471
# pylint: disable=R0915
442
472
def _instrument (
443
473
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 ,
446
476
):
447
477
_traced_execute_command = _traced_execute_factory (
448
478
tracer , request_hook , response_hook
@@ -513,11 +543,11 @@ def _instrument(
513
543
)
514
544
515
545
516
- def _instrument_connection (
546
+ def _instrument_client (
517
547
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 ,
521
551
):
522
552
# first, handle async clients and cluster clients
523
553
_async_traced_execute = _async_traced_execute_factory (
@@ -589,23 +619,48 @@ def _pipeline_wrapper(func, instance, args, kwargs):
589
619
590
620
591
621
class RedisInstrumentor (BaseInstrumentor ):
592
- """An instrumentor for Redis.
593
-
594
- See `BaseInstrumentor`
595
- """
596
-
597
622
@staticmethod
598
623
def _get_tracer (** kwargs ):
599
624
tracer_provider = kwargs .get ("tracer_provider" )
600
- return trace . get_tracer (
625
+ return get_tracer (
601
626
__name__ ,
602
627
__version__ ,
603
628
tracer_provider = tracer_provider ,
604
629
schema_url = "https://opentelemetry.io/schemas/1.11.0" ,
605
630
)
606
631
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
+ )
609
664
610
665
def _instrument (self , ** kwargs : Any ):
611
666
"""Instruments the redis module
@@ -652,13 +707,42 @@ def _uninstrument(self, **kwargs: Any):
652
707
unwrap (redis .asyncio .cluster .ClusterPipeline , "execute" )
653
708
654
709
@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 ,
657
719
):
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
+ """
658
742
if not hasattr (client , INSTRUMENTATION_ATTR ):
659
743
setattr (client , INSTRUMENTATION_ATTR , False )
660
744
if not getattr (client , INSTRUMENTATION_ATTR ):
661
- _instrument_connection (
745
+ _instrument_client (
662
746
client ,
663
747
RedisInstrumentor ._get_tracer (tracer_provider = tracer_provider ),
664
748
request_hook = request_hook ,
@@ -671,7 +755,18 @@ def instrument_connection(
671
755
)
672
756
673
757
@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
+ """
675
770
if getattr (client , INSTRUMENTATION_ATTR ):
676
771
# for all clients we need to unwrap execute_command and pipeline functions
677
772
unwrap (client , "execute_command" )
@@ -685,3 +780,7 @@ def uninstrument_connection(client):
685
780
"Attempting to un-instrument Redis connection that wasn't instrumented"
686
781
)
687
782
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