From 8b72ee1b56d0a7ef72449fc4aea98395b261d23b Mon Sep 17 00:00:00 2001 From: Itay Gibel Date: Tue, 14 Sep 2021 10:14:30 +0300 Subject: [PATCH] adding reqeust hooks and documentation --- CHANGELOG.md | 2 +- .../instrumentation/redis/__init__.py | 49 ++++++++++++++++++- .../tests/test_redis.py | 30 ++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8849d756a5..b6a2007bff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.5.0-0.24b0...HEAD) ### Added -- `opentelemetry-instrumentation-redis` added response_hook callback passed as an argument to the instrument method. +- `opentelemetry-instrumentation-redis` added request_hook and response_hook callbacks passed as arguments to the instrument method. ([#669](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/669)) ### Changed diff --git a/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py b/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py index 0266f56475..4437bea77f 100644 --- a/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py @@ -38,6 +38,38 @@ client = redis.StrictRedis(host="localhost", port=6379) client.get("my-key") +The `instrument` method accepts the following keyword args: + +tracer_provider (TracerProvider) - an optional tracer provider +request_hook (Callable) - a function with extra user-defined logic to be performed before performing the request + this function signature is: + def request_hook(span: Span, instance: redis.connection.Connection, args, kwargs) -> None +response_hook (Callable) - a function with extra user-defined logic to be performed after performing the request + this function signature is: + def response_hook(span: Span, instance: redis.connection.Connection, response) -> None + +for example: + +.. code: python + + from opentelemetry.instrumentation.redis import RedisInstrumentor + import redis + + def request_hook(span, instance, args, kwargs): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_request_hook", "some-value") + + def response_hook(span, instance, response): + if span and span.is_recording(): + span.set_attribute("custom_user_attribute_from_response_hook", "some-value") + + # Instrument redis with hooks + RedisInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook) + + # This will report a span with the default settings and the custom attributes added from the hooks + client = redis.StrictRedis(host="localhost", port=6379) + client.get("my-key") + API --- """ @@ -61,6 +93,11 @@ _DEFAULT_SERVICE = "redis" +_RequestHookT = typing.Optional[ + typing.Callable[ + [Span, redis.connection.Connection, typing.List, typing.Dict], None + ] +] _ResponseHookT = typing.Optional[ typing.Callable[[Span, redis.connection.Connection, Any], None] ] @@ -76,7 +113,9 @@ def _set_connection_attributes(span, conn): def _instrument( - tracer, response_hook: _ResponseHookT = None, + tracer, + request_hook: _RequestHookT = None, + response_hook: _ResponseHookT = None, ): def _traced_execute_command(func, instance, args, kwargs): query = _format_command_args(args) @@ -92,6 +131,8 @@ def _traced_execute_command(func, instance, args, kwargs): span.set_attribute(SpanAttributes.DB_STATEMENT, query) _set_connection_attributes(span, instance) span.set_attribute("db.redis.args_length", len(args)) + if callable(request_hook): + request_hook(span, instance, args, kwargs) response = func(*args, **kwargs) if callable(response_hook): response_hook(span, instance, response) @@ -155,7 +196,11 @@ def _instrument(self, **kwargs): tracer = trace.get_tracer( __name__, __version__, tracer_provider=tracer_provider ) - _instrument(tracer, response_hook=kwargs.get("response_hook")) + _instrument( + tracer, + request_hook=kwargs.get("request_hook"), + response_hook=kwargs.get("response_hook"), + ) def _uninstrument(self, **kwargs): if redis.VERSION < (3, 0, 0): diff --git a/instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py b/instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py index 8787e7c091..3780f0c245 100644 --- a/instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py +++ b/instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py @@ -111,3 +111,33 @@ def response_hook(span, conn, response): self.assertEqual( span.attributes.get(response_attribute_name), test_value ) + + def test_request_hook(self): + redis_client = redis.Redis() + connection = redis.connection.Connection() + redis_client.connection = connection + + custom_attribute_name = "my.request.attribute" + + def request_hook(span, conn, args, kwargs): + if span and span.is_recording(): + span.set_attribute(custom_attribute_name, args[0]) + + RedisInstrumentor().uninstrument() + RedisInstrumentor().instrument( + tracer_provider=self.tracer_provider, request_hook=request_hook + ) + + test_value = "test_value" + + with mock.patch.object(connection, "send_command"): + with mock.patch.object( + redis_client, "parse_response", return_value=test_value + ): + redis_client.get("key") + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + + span = spans[0] + self.assertEqual(span.attributes.get(custom_attribute_name), "GET")