diff --git a/docker-compose.yml b/docker-compose.yml index 674d589..5573b00 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,7 +27,7 @@ services: - "3306:3306" localstack: - image: localstack/localstack + image: localstack/localstack:0.10.5 environment: - SERVICES=dynamodb,s3 ports: diff --git a/opentracing_instrumentation/client_hooks/boto3.py b/opentracing_instrumentation/client_hooks/boto3.py index 2ce6b92..3c3f5dd 100644 --- a/opentracing_instrumentation/client_hooks/boto3.py +++ b/opentracing_instrumentation/client_hooks/boto3.py @@ -2,13 +2,14 @@ import logging from opentracing.ext import tags +from opentracing.scope_managers.tornado import TornadoScopeManager +import opentracing.tracer from tornado.stack_context import wrap as keep_stack_context from opentracing_instrumentation import utils -from ..request_context import get_current_span, span_in_stack_context +from ..request_context import get_current_span, span_in_stack_context, span_in_context from ._patcher import Patcher - try: from boto3.resources.action import ServiceAction from boto3.s3 import inject as s3_functions @@ -25,6 +26,28 @@ logger = logging.getLogger(__name__) +class _DummyStackContext(object): + """ + Stack context that restores previous scope after exit. + Will be returned by helper `_span_in_stack_context` when tracer scope + manager is not `TornadoScopeManager`. + """ + def __init__(self, context): + self._context = context + + def __enter__(self): + # Needed for compatibility with `span_in_stack_context`. + return lambda: None + + def __exit__(self, exc_type, exc_val, exc_tb): + if self._context: + self._context.close() + +def _span_in_stack_context(span): + if isinstance(opentracing.tracer.scope_manager, TornadoScopeManager): + return span_in_stack_context(span) + else: + return _DummyStackContext(span_in_context(span)) class Boto3Patcher(Patcher): applicable = '_service_action_call' in globals() @@ -134,7 +157,7 @@ def perform_call(self, original_func, kind, service_name, operation_name, span.set_tag(tags.COMPONENT, 'boto3') span.set_tag('boto3.service_name', service_name) - with span, span_in_stack_context(span): + with span, _span_in_stack_context(span): try: response = original_func(*args, **kwargs) except ClientError as error: diff --git a/setup.py b/setup.py index 1faebf6..9e5b141 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='opentracing_instrumentation', - version='3.3.2.dev0', + version='3.3.2rc1', author='Yuri Shkuro', author_email='ys@uber.com', description='Tracing Instrumentation using OpenTracing API ' diff --git a/tests/conftest.py b/tests/conftest.py index b664971..74ad023 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,6 +21,7 @@ import opentracing import pytest from opentracing.scope_managers.tornado import TornadoScopeManager +from opentracing.scope_managers.asyncio import AsyncioScopeManager def _get_tracers(scope_manager=None): @@ -52,3 +53,12 @@ def thread_safe_tracer(): yield dummy_tracer finally: opentracing.tracer = old_tracer + + +@pytest.fixture +def thread_safe_tracer_non_tornado(): + old_tracer, dummy_tracer = _get_tracers(AsyncioScopeManager()) + try: + yield dummy_tracer + finally: + opentracing.tracer = old_tracer diff --git a/tests/opentracing_instrumentation/test_boto3.py b/tests/opentracing_instrumentation/test_boto3.py index b8f2a08..e764978 100644 --- a/tests/opentracing_instrumentation/test_boto3.py +++ b/tests/opentracing_instrumentation/test_boto3.py @@ -46,7 +46,7 @@ def create_users_table(dynamodb): @pytest.fixture def dynamodb_mock(): import moto - with moto.mock_dynamodb2(): + with moto.mock_dynamodb(): dynamodb = boto3.resource('dynamodb', region_name='us-east-1') create_users_table(dynamodb) yield dynamodb @@ -175,24 +175,40 @@ def is_moto_presented(): def test_boto3_dynamodb(thread_safe_tracer, dynamodb): _test_dynamodb(dynamodb, thread_safe_tracer) +@pytest.mark.skipif(not is_dynamodb_running(), + reason='DynamoDB is not running or cannot connect') +def test_boto3_dynamodb_non_tornado(thread_safe_tracer_non_tornado, dynamodb): + _test_dynamodb(dynamodb, thread_safe_tracer_non_tornado) @pytest.mark.skipif(not is_moto_presented(), reason='moto module is not presented') def test_boto3_dynamodb_with_moto(thread_safe_tracer, dynamodb_mock): _test_dynamodb(dynamodb_mock, thread_safe_tracer) +@pytest.mark.skipif(not is_moto_presented(), + reason='moto module is not presented') +def test_boto3_dynamodb_with_moto_non_tornado(thread_safe_tracer_non_tornado, dynamodb_mock): + _test_dynamodb(dynamodb_mock, thread_safe_tracer_non_tornado) @pytest.mark.skipif(not is_s3_running(), reason='S3 is not running or cannot connect') def test_boto3_s3(s3, thread_safe_tracer): _test_s3(s3, thread_safe_tracer) +@pytest.mark.skipif(not is_s3_running(), + reason='S3 is not running or cannot connect') +def test_boto3_s3_non_tornado(s3, thread_safe_tracer_non_tornado): + _test_s3(s3, thread_safe_tracer_non_tornado) @pytest.mark.skipif(not is_moto_presented(), reason='moto module is not presented') def test_boto3_s3_with_moto(s3_mock, thread_safe_tracer): _test_s3(s3_mock, thread_safe_tracer) +@pytest.mark.skipif(not is_moto_presented(), + reason='moto module is not presented') +def test_boto3_s3_with_moto_non_tornado(s3_mock, thread_safe_tracer_non_tornado): + _test_s3(s3_mock, thread_safe_tracer_non_tornado) @testfixtures.log_capture() def test_boto3_s3_missing_func_instrumentation(capture):