diff --git a/src/instana/__init__.py b/src/instana/__init__.py index 6384a2af..7f870b6a 100644 --- a/src/instana/__init__.py +++ b/src/instana/__init__.py @@ -178,7 +178,7 @@ def boot_agent(): psycopg2, # noqa: F401 pymongo, # noqa: F401 pymysql, # noqa: F401 - # redis, # noqa: F401 + redis, # noqa: F401 # sqlalchemy, # noqa: F401 starlette_inst, # noqa: F401 sanic_inst, # noqa: F401 diff --git a/src/instana/instrumentation/redis.py b/src/instana/instrumentation/redis.py index 5c9ed522..ec439ef4 100644 --- a/src/instana/instrumentation/redis.py +++ b/src/instana/instrumentation/redis.py @@ -2,94 +2,115 @@ # (c) Copyright Instana Inc. 2018 +from typing import Any, Callable, Dict, Tuple import wrapt -from ..log import logger -from ..util.traceutils import get_tracer_tuple, tracing_is_off - +from instana.log import logger +from instana.span.span import InstanaSpan +from instana.util.traceutils import get_tracer_tuple, tracing_is_off try: import redis EXCLUDED_PARENT_SPANS = ["redis", "celery-client", "celery-worker"] - def collect_tags(span, instance, args, kwargs): + def collect_attributes( + span: InstanaSpan, + instance: redis.client.Redis, + args: Tuple[object, ...], + kwargs: Dict[str, Any], + ) -> None: try: ckw = instance.connection_pool.connection_kwargs - span.set_tag("driver", "redis-py") + span.set_attribute("driver", "redis-py") - host = ckw.get('host', None) - port = ckw.get('port', '6379') - db = ckw.get('db', None) + host = ckw.get("host", None) + port = ckw.get("port", "6379") + db = ckw.get("db", None) - if host is not None: - url = "redis://%s:%s" % (host, port) + if host: + url = f"redis://{host}:{port}" if db is not None: - url = url + "/%s" % db - span.set_tag('connection', url) - - except: - logger.debug("redis.collect_tags non-fatal error", exc_info=True) - - return span - - - def execute_command_with_instana(wrapped, instance, args, kwargs): + url = f"{url}/{db}" + span.set_attribute("connection", url) + except Exception: + logger.debug("redis.collect_attributes non-fatal error", exc_info=True) + + def execute_command_with_instana( + wrapped: Callable[..., object], + instance: redis.client.Redis, + args: Tuple[object, ...], + kwargs: Dict[str, Any], + ) -> object: tracer, parent_span, operation_name = get_tracer_tuple() + parent_context = parent_span.get_span_context() if parent_span else None # If we're not tracing, just return - if (tracing_is_off() or (operation_name in EXCLUDED_PARENT_SPANS)): + if tracing_is_off() or (operation_name in EXCLUDED_PARENT_SPANS): return wrapped(*args, **kwargs) - with tracer.start_active_span("redis", child_of=parent_span) as scope: + with tracer.start_as_current_span("redis", span_context=parent_context) as span: try: - collect_tags(scope.span, instance, args, kwargs) - if (len(args) > 0): - scope.span.set_tag("command", args[0]) + collect_attributes(span, instance, args, kwargs) + if len(args) > 0: + span.set_attribute("command", args[0]) rv = wrapped(*args, **kwargs) - except Exception as e: - scope.span.log_exception(e) + except Exception as exc: + span.record_exception(exc) raise else: return rv - - def execute_with_instana(wrapped, instance, args, kwargs): + def execute_with_instana( + wrapped: Callable[..., object], + instance: redis.client.Redis, + args: Tuple[object, ...], + kwargs: Dict[str, Any], + ) -> object: tracer, parent_span, operation_name = get_tracer_tuple() + parent_context = parent_span.get_span_context() if parent_span else None # If we're not tracing, just return - if (tracing_is_off() or (operation_name in EXCLUDED_PARENT_SPANS)): + if tracing_is_off() or (operation_name in EXCLUDED_PARENT_SPANS): return wrapped(*args, **kwargs) - with tracer.start_active_span("redis", child_of=parent_span) as scope: + with tracer.start_as_current_span("redis", span_context=parent_context) as span: try: - collect_tags(scope.span, instance, args, kwargs) - scope.span.set_tag("command", 'PIPELINE') + collect_attributes(span, instance, args, kwargs) + span.set_attribute("command", "PIPELINE") pipe_cmds = [] for e in instance.command_stack: pipe_cmds.append(e[0][0]) - scope.span.set_tag("subCommands", pipe_cmds) + span.set_attribute("subCommands", pipe_cmds) except Exception as e: # If anything breaks during K/V collection, just log a debug message logger.debug("Error collecting pipeline commands", exc_info=True) try: rv = wrapped(*args, **kwargs) - except Exception as e: - scope.span.log_exception(e) + except Exception as exc: + span.record_exception(exc) raise else: return rv - if redis.VERSION < (3,0,0): - wrapt.wrap_function_wrapper('redis.client', 'BasePipeline.execute', execute_with_instana) - wrapt.wrap_function_wrapper('redis.client', 'StrictRedis.execute_command', execute_command_with_instana) + if redis.VERSION < (3, 0, 0): + wrapt.wrap_function_wrapper( + "redis.client", "BasePipeline.execute", execute_with_instana + ) + wrapt.wrap_function_wrapper( + "redis.client", "StrictRedis.execute_command", execute_command_with_instana + ) else: - wrapt.wrap_function_wrapper('redis.client', 'Pipeline.execute', execute_with_instana) - wrapt.wrap_function_wrapper('redis.client', 'Redis.execute_command', execute_command_with_instana) + wrapt.wrap_function_wrapper( + "redis.client", "Pipeline.execute", execute_with_instana + ) + wrapt.wrap_function_wrapper( + "redis.client", "Redis.execute_command", execute_command_with_instana + ) logger.debug("Instrumenting redis") except ImportError: diff --git a/tests/clients/test_redis.py b/tests/clients/test_redis.py index e01a139b..4fa93e5c 100644 --- a/tests/clients/test_redis.py +++ b/tests/clients/test_redis.py @@ -1,376 +1,456 @@ # (c) Copyright IBM Corp. 2021 # (c) Copyright Instana Inc. 2020 -import unittest +import logging +from typing import Generator +from unittest.mock import patch +import pytest import redis -from redis.sentinel import Sentinel -from ..helpers import testenv +from instana.span.span import get_current_span +from tests.helpers import testenv from instana.singletons import agent, tracer -class TestRedis(unittest.TestCase): - def setUp(self): - """ Clear all spans before a test run """ - self.recorder = tracer.recorder +class TestRedis: + @pytest.fixture(autouse=True) + def _resource(self) -> Generator[None, None, None]: + """Clear all spans before a test run""" + self.recorder = tracer.span_processor self.recorder.clear_spans() - - # self.sentinel = Sentinel([(testenv['redis_host'], 26379)], socket_timeout=0.1) - # self.sentinel_master = self.sentinel.discover_master('mymaster') - # self.client = redis.Redis(host=self.sentinel_master[0]) - - self.client = redis.Redis(host=testenv['redis_host']) - - def tearDown(self): - """ Ensure that allow_exit_as_root has the default value """ + self.client = redis.Redis(host=testenv["redis_host"], db=testenv["redis_db"]) + yield agent.options.allow_exit_as_root = False - def test_vanilla(self): - self.client.set('instrument', 'piano') - result = self.client.get('instrument') - - def test_set_get(self): + def test_set_get(self) -> None: result = None - with tracer.start_active_span('test'): - self.client.set('foox', 'barX') - self.client.set('fooy', 'barY') - result = self.client.get('foox') + with tracer.start_as_current_span("test"): + self.client.set("foox", "barX") + self.client.set("fooy", "barY") + result = self.client.get("foox") spans = self.recorder.queued_spans() - self.assertEqual(4, len(spans)) + assert len(spans) == 4 - self.assertEqual(b'barX', result) + assert result == b"barX" rs1_span = spans[0] rs2_span = spans[1] rs3_span = spans[2] test_span = spans[3] - self.assertIsNone(tracer.active_span) + current_span = get_current_span() + assert not current_span.is_recording() # Same traceId - self.assertEqual(test_span.t, rs1_span.t) - self.assertEqual(test_span.t, rs2_span.t) - self.assertEqual(test_span.t, rs3_span.t) + assert rs1_span.t == test_span.t + assert rs2_span.t == test_span.t + assert rs3_span.t == test_span.t # Parent relationships - self.assertEqual(rs1_span.p, test_span.s) - self.assertEqual(rs2_span.p, test_span.s) - self.assertEqual(rs3_span.p, test_span.s) + assert rs1_span.p == test_span.s + assert rs2_span.p == test_span.s + assert rs3_span.p == test_span.s # Error logging - self.assertIsNone(test_span.ec) - self.assertIsNone(rs1_span.ec) - self.assertIsNone(rs2_span.ec) - self.assertIsNone(rs3_span.ec) + assert not test_span.ec + assert not rs1_span.ec + assert not rs2_span.ec + assert not rs3_span.ec # Redis span 1 - self.assertEqual('redis', rs1_span.n) - self.assertFalse('custom' in rs1_span.data) - self.assertTrue('redis' in rs1_span.data) - - self.assertEqual('redis-py', rs1_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs1_span.data["redis"]["connection"]) - self.assertEqual("SET", rs1_span.data["redis"]["command"]) - self.assertIsNone(rs1_span.data["redis"]["error"]) - - self.assertIsNotNone(rs1_span.stack) - self.assertTrue(type(rs1_span.stack) is list) - self.assertGreater(len(rs1_span.stack), 0) + assert rs1_span.n == "redis" + assert "custom" not in rs1_span.data + assert "redis" in rs1_span.data + + assert rs1_span.data["redis"]["driver"] == "redis-py" + assert ( + rs1_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs1_span.data["redis"]["command"] == "SET" + assert not rs1_span.data["redis"]["error"] + + assert rs1_span.stack + assert isinstance(rs1_span.stack, list) + assert len(rs1_span.stack) > 0 # Redis span 2 - self.assertEqual('redis', rs2_span.n) - self.assertFalse('custom' in rs2_span.data) - self.assertTrue('redis' in rs2_span.data) - - self.assertEqual('redis-py', rs2_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs2_span.data["redis"]["connection"]) - self.assertEqual("SET", rs2_span.data["redis"]["command"]) - self.assertIsNone(rs2_span.data["redis"]["error"]) - - self.assertIsNotNone(rs2_span.stack) - self.assertTrue(type(rs2_span.stack) is list) - self.assertGreater(len(rs2_span.stack), 0) + assert rs2_span.n == "redis" + assert "custom" not in rs2_span.data + assert "redis" in rs2_span.data + + assert rs2_span.data["redis"]["driver"] == "redis-py" + assert ( + rs2_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs2_span.data["redis"]["command"] == "SET" + assert not rs2_span.data["redis"]["error"] + + assert rs2_span.stack + assert isinstance(rs2_span.stack, list) + assert len(rs2_span.stack) > 0 # Redis span 3 - self.assertEqual('redis', rs3_span.n) - self.assertFalse('custom' in rs3_span.data) - self.assertTrue('redis' in rs3_span.data) - - self.assertEqual('redis-py', rs3_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs3_span.data["redis"]["connection"]) - self.assertEqual("GET", rs3_span.data["redis"]["command"]) - self.assertIsNone(rs3_span.data["redis"]["error"]) - - self.assertIsNotNone(rs3_span.stack) - self.assertTrue(type(rs3_span.stack) is list) - self.assertGreater(len(rs3_span.stack), 0) - - def test_set_get_as_root_span(self): + assert rs3_span.n == "redis" + assert "custom" not in rs3_span.data + assert "redis" in rs3_span.data + + assert rs3_span.data["redis"]["driver"] == "redis-py" + assert ( + rs3_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs3_span.data["redis"]["command"] == "GET" + assert not rs3_span.data["redis"]["error"] + + assert rs3_span.stack + assert isinstance(rs3_span.stack, list) + assert len(rs3_span.stack) > 0 + + def test_set_get_as_root_span(self) -> None: agent.options.allow_exit_as_root = True - self.client.set('foox', 'barX') - self.client.set('fooy', 'barY') - result = self.client.get('foox') + self.client.set("foox", "barX") + self.client.set("fooy", "barY") + result = self.client.get("foox") spans = self.recorder.queued_spans() - self.assertEqual(3, len(spans)) + assert len(spans) == 3 - self.assertEqual(b'barX', result) + assert result == b"barX" rs1_span = spans[0] rs2_span = spans[1] rs3_span = spans[2] - self.assertIsNone(tracer.active_span) + current_span = get_current_span() + assert not current_span.is_recording() # Parent relationships - self.assertEqual(rs1_span.p, None) - self.assertEqual(rs2_span.p, None) - self.assertEqual(rs3_span.p, None) + assert not rs1_span.p + assert not rs2_span.p + assert not rs3_span.p # Error logging - self.assertIsNone(rs1_span.ec) - self.assertIsNone(rs2_span.ec) - self.assertIsNone(rs3_span.ec) + assert not rs1_span.ec + assert not rs2_span.ec + assert not rs3_span.ec # Redis span 1 - self.assertEqual('redis', rs1_span.n) - self.assertFalse('custom' in rs1_span.data) - self.assertTrue('redis' in rs1_span.data) - - self.assertEqual('redis-py', rs1_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs1_span.data["redis"]["connection"]) - self.assertEqual("SET", rs1_span.data["redis"]["command"]) - self.assertIsNone(rs1_span.data["redis"]["error"]) - - self.assertIsNotNone(rs1_span.stack) - self.assertTrue(type(rs1_span.stack) is list) - self.assertGreater(len(rs1_span.stack), 0) + assert rs1_span.n == "redis" + assert "custom" not in rs1_span.data + assert "redis" in rs1_span.data + + assert rs1_span.data["redis"]["driver"] == "redis-py" + assert ( + rs1_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs1_span.data["redis"]["command"] == "SET" + assert not rs1_span.data["redis"]["error"] + + assert rs1_span.stack + assert isinstance(rs1_span.stack, list) + assert len(rs1_span.stack) > 0 # Redis span 2 - self.assertEqual('redis', rs2_span.n) - self.assertFalse('custom' in rs2_span.data) - self.assertTrue('redis' in rs2_span.data) - - self.assertEqual('redis-py', rs2_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs2_span.data["redis"]["connection"]) - self.assertEqual("SET", rs2_span.data["redis"]["command"]) - self.assertIsNone(rs2_span.data["redis"]["error"]) - - self.assertIsNotNone(rs2_span.stack) - self.assertTrue(type(rs2_span.stack) is list) - self.assertGreater(len(rs2_span.stack), 0) + assert rs2_span.n == "redis" + assert "custom" not in rs2_span.data + assert "redis" in rs2_span.data + + assert rs2_span.data["redis"]["driver"] == "redis-py" + assert ( + rs2_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs2_span.data["redis"]["command"] == "SET" + assert not rs2_span.data["redis"]["error"] + + assert rs2_span.stack + assert isinstance(rs2_span.stack, list) + assert len(rs2_span.stack) > 0 # Redis span 3 - self.assertEqual('redis', rs3_span.n) - self.assertFalse('custom' in rs3_span.data) - self.assertTrue('redis' in rs3_span.data) - - self.assertEqual('redis-py', rs3_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs3_span.data["redis"]["connection"]) - self.assertEqual("GET", rs3_span.data["redis"]["command"]) - self.assertIsNone(rs3_span.data["redis"]["error"]) - - self.assertIsNotNone(rs3_span.stack) - self.assertTrue(type(rs3_span.stack) is list) - self.assertGreater(len(rs3_span.stack), 0) - - def test_set_incr_get(self): + assert rs3_span.n == "redis" + assert "custom" not in rs3_span.data + assert "redis" in rs3_span.data + + assert rs3_span.data["redis"]["driver"] == "redis-py" + assert ( + rs3_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs3_span.data["redis"]["command"] == "GET" + assert not rs3_span.data["redis"]["error"] + + assert rs3_span.stack + assert isinstance(rs3_span.stack, list) + assert len(rs3_span.stack) > 0 + + def test_set_incr_get(self) -> None: result = None - with tracer.start_active_span('test'): - self.client.set('counter', '10') - self.client.incr('counter') - result = self.client.get('counter') + with tracer.start_as_current_span("test"): + self.client.set("counter", "10") + self.client.incr("counter") + result = self.client.get("counter") spans = self.recorder.queued_spans() - self.assertEqual(4, len(spans)) + assert len(spans) == 4 - self.assertEqual(b'11', result) + assert result == b"11" rs1_span = spans[0] rs2_span = spans[1] rs3_span = spans[2] test_span = spans[3] - self.assertIsNone(tracer.active_span) + current_span = get_current_span() + assert not current_span.is_recording() # Same traceId - self.assertEqual(test_span.t, rs1_span.t) - self.assertEqual(test_span.t, rs2_span.t) - self.assertEqual(test_span.t, rs3_span.t) + assert rs1_span.t == test_span.t + assert rs2_span.t == test_span.t + assert rs3_span.t == test_span.t # Parent relationships - self.assertEqual(rs1_span.p, test_span.s) - self.assertEqual(rs2_span.p, test_span.s) - self.assertEqual(rs3_span.p, test_span.s) + assert rs1_span.p == test_span.s + assert rs2_span.p == test_span.s + assert rs3_span.p == test_span.s # Error logging - self.assertIsNone(test_span.ec) - self.assertIsNone(rs1_span.ec) - self.assertIsNone(rs2_span.ec) - self.assertIsNone(rs3_span.ec) + assert not test_span.ec + assert not rs1_span.ec + assert not rs2_span.ec + assert not rs3_span.ec # Redis span 1 - self.assertEqual('redis', rs1_span.n) - self.assertFalse('custom' in rs1_span.data) - self.assertTrue('redis' in rs1_span.data) - - self.assertEqual('redis-py', rs1_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs1_span.data["redis"]["connection"]) - self.assertEqual("SET", rs1_span.data["redis"]["command"]) - self.assertIsNone(rs1_span.data["redis"]["error"]) - - self.assertIsNotNone(rs1_span.stack) - self.assertTrue(type(rs1_span.stack) is list) - self.assertGreater(len(rs1_span.stack), 0) + assert rs1_span.n == "redis" + assert "custom" not in rs1_span.data + assert "redis" in rs1_span.data + + assert rs1_span.data["redis"]["driver"] == "redis-py" + assert ( + rs1_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs1_span.data["redis"]["command"] == "SET" + assert not rs1_span.data["redis"]["error"] + + assert rs1_span.stack + assert isinstance(rs1_span.stack, list) + assert len(rs1_span.stack) > 0 # Redis span 2 - self.assertEqual('redis', rs2_span.n) - self.assertFalse('custom' in rs2_span.data) - self.assertTrue('redis' in rs2_span.data) - - self.assertEqual('redis-py', rs2_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs2_span.data["redis"]["connection"]) - self.assertEqual("INCRBY", rs2_span.data["redis"]["command"]) - self.assertIsNone(rs2_span.data["redis"]["error"]) - - self.assertIsNotNone(rs2_span.stack) - self.assertTrue(type(rs2_span.stack) is list) - self.assertGreater(len(rs2_span.stack), 0) + assert rs2_span.n == "redis" + assert "custom" not in rs2_span.data + assert "redis" in rs2_span.data + + assert rs2_span.data["redis"]["driver"] == "redis-py" + assert ( + rs2_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs2_span.data["redis"]["command"] == "INCRBY" + assert not rs2_span.data["redis"]["error"] + + assert rs2_span.stack + assert isinstance(rs2_span.stack, list) + assert len(rs2_span.stack) > 0 # Redis span 3 - self.assertEqual('redis', rs3_span.n) - self.assertFalse('custom' in rs3_span.data) - self.assertTrue('redis' in rs3_span.data) - - self.assertEqual('redis-py', rs3_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs3_span.data["redis"]["connection"]) - self.assertEqual("GET", rs3_span.data["redis"]["command"]) - self.assertIsNone(rs3_span.data["redis"]["error"]) - - self.assertIsNotNone(rs3_span.stack) - self.assertTrue(type(rs3_span.stack) is list) - self.assertGreater(len(rs3_span.stack), 0) - - def test_old_redis_client(self): + assert rs3_span.n == "redis" + assert "custom" not in rs3_span.data + assert "redis" in rs3_span.data + + assert rs3_span.data["redis"]["driver"] == "redis-py" + assert ( + rs3_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs3_span.data["redis"]["command"] == "GET" + assert not rs3_span.data["redis"]["error"] + + assert rs3_span.stack + assert isinstance(rs3_span.stack, list) + assert len(rs3_span.stack) > 0 + + def test_old_redis_client(self) -> None: result = None - with tracer.start_active_span('test'): - self.client.set('foox', 'barX') - self.client.set('fooy', 'barY') - result = self.client.get('foox') + with tracer.start_as_current_span("test"): + self.client.set("foox", "barX") + self.client.set("fooy", "barY") + result = self.client.get("foox") spans = self.recorder.queued_spans() - self.assertEqual(4, len(spans)) + assert len(spans) == 4 - self.assertEqual(b'barX', result) + assert result == b"barX" rs1_span = spans[0] rs2_span = spans[1] rs3_span = spans[2] test_span = spans[3] - self.assertIsNone(tracer.active_span) + current_span = get_current_span() + assert not current_span.is_recording() # Same traceId - self.assertEqual(test_span.t, rs1_span.t) - self.assertEqual(test_span.t, rs2_span.t) - self.assertEqual(test_span.t, rs3_span.t) + assert rs1_span.t == test_span.t + assert rs2_span.t == test_span.t + assert rs3_span.t == test_span.t # Parent relationships - self.assertEqual(rs1_span.p, test_span.s) - self.assertEqual(rs2_span.p, test_span.s) - self.assertEqual(rs3_span.p, test_span.s) + assert rs1_span.p == test_span.s + assert rs2_span.p == test_span.s + assert rs3_span.p == test_span.s # Error logging - self.assertIsNone(test_span.ec) - self.assertIsNone(rs1_span.ec) - self.assertIsNone(rs2_span.ec) - self.assertIsNone(rs3_span.ec) + assert not test_span.ec + assert not rs1_span.ec + assert not rs2_span.ec + assert not rs3_span.ec # Redis span 1 - self.assertEqual('redis', rs1_span.n) - self.assertFalse('custom' in rs1_span.data) - self.assertTrue('redis' in rs1_span.data) - - self.assertEqual('redis-py', rs1_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs1_span.data["redis"]["connection"]) - self.assertEqual("SET", rs1_span.data["redis"]["command"]) - self.assertIsNone(rs1_span.data["redis"]["error"]) - - self.assertIsNotNone(rs1_span.stack) - self.assertTrue(type(rs1_span.stack) is list) - self.assertGreater(len(rs1_span.stack), 0) + assert rs1_span.n == "redis" + assert "custom" not in rs1_span.data + assert "redis" in rs1_span.data + + assert rs1_span.data["redis"]["driver"] == "redis-py" + assert ( + rs1_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs1_span.data["redis"]["command"] == "SET" + assert not rs1_span.data["redis"]["error"] + + assert rs1_span.stack + assert isinstance(rs1_span.stack, list) + assert len(rs1_span.stack) > 0 # Redis span 2 - self.assertEqual('redis', rs2_span.n) - self.assertFalse('custom' in rs2_span.data) - self.assertTrue('redis' in rs2_span.data) + assert rs2_span.n == "redis" + assert "custom" not in rs2_span.data + assert "redis" in rs2_span.data - self.assertEqual('redis-py', rs2_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs2_span.data["redis"]["connection"]) - self.assertEqual("SET", rs2_span.data["redis"]["command"]) - self.assertIsNone(rs2_span.data["redis"]["error"]) + assert rs2_span.data["redis"]["driver"] == "redis-py" + assert ( + rs2_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) - self.assertIsNotNone(rs2_span.stack) - self.assertTrue(type(rs2_span.stack) is list) - self.assertGreater(len(rs2_span.stack), 0) + assert rs2_span.data["redis"]["command"] == "SET" + assert not rs2_span.data["redis"]["error"] - # Redis span 3 - self.assertEqual('redis', rs3_span.n) - self.assertFalse('custom' in rs3_span.data) - self.assertTrue('redis' in rs3_span.data) - - self.assertEqual('redis-py', rs3_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs3_span.data["redis"]["connection"]) - self.assertEqual("GET", rs3_span.data["redis"]["command"]) - self.assertIsNone(rs3_span.data["redis"]["error"]) + assert rs2_span.stack + assert isinstance(rs2_span.stack, list) + assert len(rs2_span.stack) > 0 - self.assertIsNotNone(rs3_span.stack) - self.assertTrue(type(rs3_span.stack) is list) - self.assertGreater(len(rs3_span.stack), 0) - - def test_pipelined_requests(self): + # Redis span 3 + assert rs3_span.n == "redis" + assert "custom" not in rs3_span.data + assert "redis" in rs3_span.data + + assert rs3_span.data["redis"]["driver"] == "redis-py" + assert ( + rs3_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs3_span.data["redis"]["command"] == "GET" + assert not rs3_span.data["redis"]["error"] + + assert rs3_span.stack + assert isinstance(rs3_span.stack, list) + assert len(rs3_span.stack) > 0 + + def test_pipelined_requests(self) -> None: result = None - with tracer.start_active_span('test'): + with tracer.start_as_current_span("test"): pipe = self.client.pipeline() - pipe.set('foox', 'barX') - pipe.set('fooy', 'barY') - pipe.get('foox') + pipe.set("foox", "barX") + pipe.set("fooy", "barY") + pipe.get("foox") result = pipe.execute() spans = self.recorder.queued_spans() - self.assertEqual(2, len(spans)) + assert len(spans) == 2 - self.assertEqual([True, True, b'barX'], result) + assert result == [True, True, b"barX"] rs1_span = spans[0] test_span = spans[1] - self.assertIsNone(tracer.active_span) + current_span = get_current_span() + assert not current_span.is_recording() # Same traceId - self.assertEqual(test_span.t, rs1_span.t) + assert rs1_span.t == test_span.t # Parent relationships - self.assertEqual(rs1_span.p, test_span.s) + assert rs1_span.p == test_span.s # Error logging - self.assertIsNone(test_span.ec) - self.assertIsNone(rs1_span.ec) + assert not test_span.ec + assert not rs1_span.ec # Redis span 1 - self.assertEqual('redis', rs1_span.n) - self.assertFalse('custom' in rs1_span.data) - self.assertTrue('redis' in rs1_span.data) - - self.assertEqual('redis-py', rs1_span.data["redis"]["driver"]) - self.assertEqual("redis://%s:6379/0" % testenv['redis_host'], rs1_span.data["redis"]["connection"]) - self.assertEqual("PIPELINE", rs1_span.data["redis"]["command"]) - self.assertEqual(['SET', 'SET', 'GET'], rs1_span.data["redis"]["subCommands"]) - self.assertIsNone(rs1_span.data["redis"]["error"]) - - self.assertIsNotNone(rs1_span.stack) - self.assertTrue(type(rs1_span.stack) is list) - self.assertGreater(len(rs1_span.stack), 0) + assert rs1_span.n == "redis" + assert "custom" not in rs1_span.data + assert "redis" in rs1_span.data + + assert rs1_span.data["redis"]["driver"] == "redis-py" + assert ( + rs1_span.data["redis"]["connection"] + == f"redis://{testenv['redis_host']}:6379/0" + ) + assert rs1_span.data["redis"]["command"] == "PIPELINE" + assert rs1_span.data["redis"]["subCommands"] == ["SET", "SET", "GET"] + assert not rs1_span.data["redis"]["error"] + + assert rs1_span.stack + assert isinstance(rs1_span.stack, list) + assert len(rs1_span.stack) > 0 + + @patch( + "instana.instrumentation.redis.collect_attributes", + side_effect=Exception("test-error"), + ) + @patch("instana.span.span.InstanaSpan.record_exception") + def test_execute_command_with_instana_exception(self, mock_record_func, _) -> None: + with tracer.start_as_current_span("test"), pytest.raises( + Exception, match="test-error" + ): + self.client.set("counter", "10") + mock_record_func.assert_called() + + def test_execute_comand_with_instana_tracing_off(self) -> None: + with tracer.start_as_current_span("redis"): + response = self.client.set("counter", "10") + assert response + + def test_execute_with_instana_tracing_off(self) -> None: + result = None + with tracer.start_as_current_span("redis"): + pipe = self.client.pipeline() + pipe.set("foox", "barX") + pipe.set("fooy", "barY") + pipe.get("foox") + result = pipe.execute() + assert result == [True, True, b"barX"] + + def test_execute_with_instana_exception( + self, caplog: pytest.LogCaptureFixture + ) -> None: + caplog.set_level(logging.DEBUG, logger="instana") + with tracer.start_as_current_span("test"), patch( + "instana.instrumentation.redis.collect_attributes", + side_effect=Exception("test-error"), + ): + pipe = self.client.pipeline() + pipe.set("foox", "barX") + pipe.set("fooy", "barY") + pipe.get("foox") + pipe.execute() + assert "Error collecting pipeline commands" in caplog.messages diff --git a/tests/conftest.py b/tests/conftest.py index 425ba008..660f1245 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -39,7 +39,6 @@ # codes are finalised. collect_ignore_glob.append("*clients/test_google*") collect_ignore_glob.append("*clients/test_pika*") -collect_ignore_glob.append("*clients/test_redis*") collect_ignore_glob.append("*clients/test_sql*") collect_ignore_glob.append("*frameworks/test_celery*") diff --git a/tests/helpers.py b/tests/helpers.py index 30caf5ac..7a24bdc8 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -46,6 +46,7 @@ Redis Environment """ testenv["redis_host"] = os.environ.get("REDIS_HOST", "127.0.0.1") +testenv["redis_db"] = os.environ.get("REDIS_DB", 0) """ MongoDB Environment