[boto3sqs] Instrument `Session` and `resource` (#2161)
* [boto3sqs] Instrument `Session` and `resource` This commit addresses the following open issues: - https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1699 - https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1996 There are four ways to access the SQS API via `boto3`: - `client = boto3.client("sqs")` - `client = boto3.Session().client("sqs")` - `sqs = boto3.resource("sqs")` - `sqs = boto3.Session().resource("sqs")` The existing wrapper tied into `boto3.client` to wrap a generated `botocore.client.SQS` class. The change here covers the three missing initialization methods. * update changelog * rename duplicate test methods * implement uninstrument * [boto3sqs] Reduce number of wrapper targets There are actually 6 ways to initialize a boto3 API object. ```py boto3.client() # Using default global session boto3.resource() # Using default global session boto3.Session().client() # Using "re-exported" session.Session boto3.Session().resource() # Using "re-exported" session.Session boto3.session.Session().client() # Using session.Session directly boto3.session.Session().resource() # Using session.Session directly ``` We only have to patch `session.Session.client` to catch all the cases. -b3c158c62a/boto3/session.py (L217-L229)-b3c158c62a/boto3/session.py (L446-L457)* Remove unused import --------- Co-authored-by: Matt Oberle <mattoberle@users.noreply.github.com> Co-authored-by: Diego Hurtado <ocelotl@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									4e90498bf3
								
							
						
					
					
						commit
						c644f0d7d5
					
				| 
						 | 
				
			
			@ -37,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 | 
			
		|||
 | 
			
		||||
- `opentelemetry-instrumentation-grpc` AioClientInterceptor should propagate with a Metadata object
 | 
			
		||||
  ([#2363](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2363))
 | 
			
		||||
- `opentelemetry-instrumentation-boto3sqs` Instrument Session and resource
 | 
			
		||||
  ([#2161](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2161))
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,7 @@ Usage
 | 
			
		|||
import logging
 | 
			
		||||
from typing import Any, Collection, Dict, Generator, List, Mapping, Optional
 | 
			
		||||
 | 
			
		||||
import boto3
 | 
			
		||||
import boto3.session
 | 
			
		||||
import botocore.client
 | 
			
		||||
from wrapt import wrap_function_wrapper
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -382,7 +382,7 @@ class Boto3SQSInstrumentor(BaseInstrumentor):
 | 
			
		|||
            self._decorate_sqs(type(retval))
 | 
			
		||||
            return retval
 | 
			
		||||
 | 
			
		||||
        wrap_function_wrapper(boto3, "client", client_wrapper)
 | 
			
		||||
        wrap_function_wrapper(boto3.session.Session, "client", client_wrapper)
 | 
			
		||||
 | 
			
		||||
    def _decorate_sqs(self, sqs_class: type) -> None:
 | 
			
		||||
        """
 | 
			
		||||
| 
						 | 
				
			
			@ -433,7 +433,7 @@ class Boto3SQSInstrumentor(BaseInstrumentor):
 | 
			
		|||
            self._decorate_sqs(client_cls)
 | 
			
		||||
 | 
			
		||||
    def _uninstrument(self, **kwargs: Dict[str, Any]) -> None:
 | 
			
		||||
        unwrap(boto3, "client")
 | 
			
		||||
        unwrap(boto3.session.Session, "client")
 | 
			
		||||
 | 
			
		||||
        for client_cls in botocore.client.BaseClient.__subclasses__():
 | 
			
		||||
            self._un_decorate_sqs(client_cls)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ from unittest import TestCase, mock
 | 
			
		|||
 | 
			
		||||
import boto3
 | 
			
		||||
from botocore.awsrequest import AWSResponse
 | 
			
		||||
from wrapt import BoundFunctionWrapper, FunctionWrapper
 | 
			
		||||
from wrapt import BoundFunctionWrapper
 | 
			
		||||
 | 
			
		||||
from opentelemetry.instrumentation.boto3sqs import (
 | 
			
		||||
    Boto3SQSGetter,
 | 
			
		||||
| 
						 | 
				
			
			@ -37,8 +37,17 @@ from opentelemetry.trace import SpanKind
 | 
			
		|||
from opentelemetry.trace.span import Span, format_span_id, format_trace_id
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _make_sqs_client():
 | 
			
		||||
    return boto3.client(
 | 
			
		||||
def _make_sqs_client(*, session=False):
 | 
			
		||||
    return (boto3.Session() if session else boto3).client(
 | 
			
		||||
        "sqs",
 | 
			
		||||
        region_name="us-east-1",
 | 
			
		||||
        aws_access_key_id="dummy",
 | 
			
		||||
        aws_secret_access_key="dummy",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _make_sqs_resource(*, session=False):
 | 
			
		||||
    return (boto3.Session() if session else boto3).resource(
 | 
			
		||||
        "sqs",
 | 
			
		||||
        region_name="us-east-1",
 | 
			
		||||
        aws_access_key_id="dummy",
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +57,6 @@ def _make_sqs_client():
 | 
			
		|||
 | 
			
		||||
class TestBoto3SQSInstrumentor(TestCase):
 | 
			
		||||
    def _assert_instrumented(self, client):
 | 
			
		||||
        self.assertIsInstance(boto3.client, FunctionWrapper)
 | 
			
		||||
        self.assertIsInstance(client.send_message, BoundFunctionWrapper)
 | 
			
		||||
        self.assertIsInstance(client.send_message_batch, BoundFunctionWrapper)
 | 
			
		||||
        self.assertIsInstance(client.receive_message, BoundFunctionWrapper)
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +65,17 @@ class TestBoto3SQSInstrumentor(TestCase):
 | 
			
		|||
            client.delete_message_batch, BoundFunctionWrapper
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def _assert_uninstrumented(self, client):
 | 
			
		||||
        self.assertNotIsInstance(client.send_message, BoundFunctionWrapper)
 | 
			
		||||
        self.assertNotIsInstance(
 | 
			
		||||
            client.send_message_batch, BoundFunctionWrapper
 | 
			
		||||
        )
 | 
			
		||||
        self.assertNotIsInstance(client.receive_message, BoundFunctionWrapper)
 | 
			
		||||
        self.assertNotIsInstance(client.delete_message, BoundFunctionWrapper)
 | 
			
		||||
        self.assertNotIsInstance(
 | 
			
		||||
            client.delete_message_batch, BoundFunctionWrapper
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    @contextmanager
 | 
			
		||||
    def _active_instrumentor():
 | 
			
		||||
| 
						 | 
				
			
			@ -67,19 +86,48 @@ class TestBoto3SQSInstrumentor(TestCase):
 | 
			
		|||
            Boto3SQSInstrumentor().uninstrument()
 | 
			
		||||
 | 
			
		||||
    def test_instrument_api_before_client_init(self) -> None:
 | 
			
		||||
        with self._active_instrumentor():
 | 
			
		||||
            client = _make_sqs_client()
 | 
			
		||||
            self._assert_instrumented(client)
 | 
			
		||||
        for session in (False, True):
 | 
			
		||||
            with self._active_instrumentor():
 | 
			
		||||
                client = _make_sqs_client(session=session)
 | 
			
		||||
                self._assert_instrumented(client)
 | 
			
		||||
            self._assert_uninstrumented(client)
 | 
			
		||||
 | 
			
		||||
    def test_instrument_api_after_client_init(self) -> None:
 | 
			
		||||
        client = _make_sqs_client()
 | 
			
		||||
        with self._active_instrumentor():
 | 
			
		||||
            self._assert_instrumented(client)
 | 
			
		||||
        for session in (False, True):
 | 
			
		||||
            client = _make_sqs_client(session=session)
 | 
			
		||||
            with self._active_instrumentor():
 | 
			
		||||
                self._assert_instrumented(client)
 | 
			
		||||
            self._assert_uninstrumented(client)
 | 
			
		||||
 | 
			
		||||
    def test_instrument_multiple_clients(self):
 | 
			
		||||
        with self._active_instrumentor():
 | 
			
		||||
            self._assert_instrumented(_make_sqs_client())
 | 
			
		||||
            self._assert_instrumented(_make_sqs_client())
 | 
			
		||||
        for session in (False, True):
 | 
			
		||||
            with self._active_instrumentor():
 | 
			
		||||
                self._assert_instrumented(_make_sqs_client(session=session))
 | 
			
		||||
                self._assert_instrumented(_make_sqs_client(session=session))
 | 
			
		||||
 | 
			
		||||
    def test_instrument_api_before_resource_init(self) -> None:
 | 
			
		||||
        for session in (False, True):
 | 
			
		||||
            with self._active_instrumentor():
 | 
			
		||||
                sqs = _make_sqs_resource(session=session)
 | 
			
		||||
                self._assert_instrumented(sqs.meta.client)
 | 
			
		||||
            self._assert_uninstrumented(sqs.meta.client)
 | 
			
		||||
 | 
			
		||||
    def test_instrument_api_after_resource_init(self) -> None:
 | 
			
		||||
        for session in (False, True):
 | 
			
		||||
            sqs = _make_sqs_resource(session=session)
 | 
			
		||||
            with self._active_instrumentor():
 | 
			
		||||
                self._assert_instrumented(sqs.meta.client)
 | 
			
		||||
            self._assert_uninstrumented(sqs.meta.client)
 | 
			
		||||
 | 
			
		||||
    def test_instrument_multiple_resources(self):
 | 
			
		||||
        for session in (False, True):
 | 
			
		||||
            with self._active_instrumentor():
 | 
			
		||||
                self._assert_instrumented(
 | 
			
		||||
                    _make_sqs_resource(session=session).meta.client
 | 
			
		||||
                )
 | 
			
		||||
                self._assert_instrumented(
 | 
			
		||||
                    _make_sqs_resource(session=session).meta.client
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestBoto3SQSGetter(TestCase):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue