Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions sentry_sdk/integrations/boto3/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version
from sentry_sdk.integrations.boto3._client import _patch_botocore_client
Comment thread
cursor[bot] marked this conversation as resolved.
from sentry_sdk.integrations.boto3.consts import IDENTIFIER, ORIGIN
from sentry_sdk.utils import parse_version

try:
from botocore import __version__ as BOTOCORE_VERSION
except ImportError:
raise DidNotEnable("botocore is not installed")


class Boto3Integration(Integration):
identifier = IDENTIFIER
origin = ORIGIN

@staticmethod
def setup_once() -> None:
version = parse_version(BOTOCORE_VERSION)
_check_minimum_version(Boto3Integration, version, "botocore")

_patch_botocore_client()
37 changes: 37 additions & 0 deletions sentry_sdk/integrations/boto3/_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from functools import partial
from typing import TYPE_CHECKING

from sentry_sdk.integrations import DidNotEnable
from sentry_sdk.integrations.boto3._instrumentation import (
_sentry_after_call,
_sentry_after_call_error,
_sentry_before_sign,
_sentry_request_created,
)

if TYPE_CHECKING:
from typing import Any

try:
from botocore.client import BaseClient
except ImportError:
raise DidNotEnable("botocore is not installed")


def _patch_botocore_client() -> None:
orig_init = BaseClient.__init__

def sentry_patched_init(self: "BaseClient", *args: "Any", **kwargs: "Any") -> None:
orig_init(self, *args, **kwargs)
meta = self.meta
service_id = meta.service_model.service_id
meta.events.register(
"request-created",
partial(_sentry_request_created, service_id=service_id),
)
# run after other `before-sign` handlers, allowing it to see and preserve existing baggage.
meta.events.register_last("before-sign", _sentry_before_sign)
meta.events.register("after-call", _sentry_after_call)
meta.events.register("after-call-error", _sentry_after_call_error)

BaseClient.__init__ = sentry_patched_init # type: ignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from functools import partial
from typing import TYPE_CHECKING

import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version
from sentry_sdk.integrations import DidNotEnable
from sentry_sdk.integrations.boto3.consts import IDENTIFIER, ORIGIN
from sentry_sdk.traces import StreamedSpan
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, Span
from sentry_sdk.tracing_utils import (
Expand All @@ -16,7 +16,6 @@
from sentry_sdk.utils import (
capture_internal_exceptions,
parse_url,
parse_version,
)

if TYPE_CHECKING:
Expand All @@ -26,50 +25,20 @@


try:
from botocore import __version__ as BOTOCORE_VERSION
from botocore.awsrequest import AWSRequest
from botocore.client import BaseClient
from botocore.response import StreamingBody
except ImportError:
raise DidNotEnable("botocore is not installed")


class Boto3Integration(Integration):
identifier = "boto3"
origin = f"auto.http.{identifier}"

@staticmethod
def setup_once() -> None:
version = parse_version(BOTOCORE_VERSION)
_check_minimum_version(Boto3Integration, version, "botocore")

orig_init = BaseClient.__init__

def sentry_patched_init(
self: "BaseClient", *args: "Any", **kwargs: "Any"
) -> None:
orig_init(self, *args, **kwargs)
meta = self.meta
service_id = meta.service_model.service_id
meta.events.register(
"request-created",
partial(_sentry_request_created, service_id=service_id),
)
# run after other `before-sign` handlers, allowing it to see and preserve existing baggage.
meta.events.register_last("before-sign", _sentry_before_sign)
meta.events.register("after-call", _sentry_after_call)
meta.events.register("after-call-error", _sentry_after_call_error)

BaseClient.__init__ = sentry_patched_init # type: ignore


def _sentry_request_created(
service_id: "ServiceId", request: "AWSRequest", operation_name: str, **kwargs: "Any"
) -> None:

description = "aws.%s.%s" % (service_id.hyphenize(), operation_name)

client = sentry_sdk.get_client()
if client.get_integration(Boto3Integration) is None:
if client.get_integration(IDENTIFIER) is None:
return

parsed_url = None
Expand All @@ -93,7 +62,7 @@ def _sentry_request_created(
name=description,
attributes={
"sentry.op": OP.HTTP_CLIENT,
"sentry.origin": Boto3Integration.origin,
"sentry.origin": ORIGIN,
SPANDATA.RPC_METHOD: f"{service_id}/{operation_name}",
},
)
Expand All @@ -105,7 +74,7 @@ def _sentry_request_created(
span = sentry_sdk.start_span(
op=OP.HTTP_CLIENT,
name=description,
origin=Boto3Integration.origin,
origin=ORIGIN,
)

if parsed_url:
Expand Down Expand Up @@ -141,8 +110,9 @@ def _sentry_request_created(
def _sentry_before_sign(
request: "AWSRequest", signature_version: "Any", **kwargs: "Any"
) -> None:

client = sentry_sdk.get_client()
if client.get_integration(Boto3Integration) is None:
if client.get_integration(IDENTIFIER) is None:
return

with capture_internal_exceptions():
Expand Down Expand Up @@ -214,14 +184,14 @@ def _sentry_after_call(
parent_span=span,
attributes={
"sentry.op": OP.HTTP_CLIENT_STREAM,
"sentry.origin": Boto3Integration.origin,
"sentry.origin": ORIGIN,
},
)
else:
streaming_span = span.start_child(
op=OP.HTTP_CLIENT_STREAM,
name=span.description,
origin=Boto3Integration.origin,
origin=ORIGIN,
)

orig_read = body.read
Expand Down
2 changes: 2 additions & 0 deletions sentry_sdk/integrations/boto3/consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
IDENTIFIER = "boto3"
ORIGIN = f"auto.http.{IDENTIFIER}"
6 changes: 6 additions & 0 deletions tests/integrations/boto3/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from sentry_sdk.integrations.boto3 import Boto3Integration


def test_public_api():
assert Boto3Integration.__module__ == "sentry_sdk.integrations.boto3"
assert Boto3Integration.identifier == "boto3"
4 changes: 2 additions & 2 deletions tests/integrations/boto3/test_s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def test_omit_url_data_if_parsing_fails(
items = capture_items("span")

with mock.patch(
"sentry_sdk.integrations.boto3.parse_url",
"sentry_sdk.integrations.boto3._instrumentation.parse_url",
side_effect=ValueError,
):
with sentry_sdk.traces.start_span(
Expand Down Expand Up @@ -294,7 +294,7 @@ def test_omit_url_data_if_parsing_fails(
events = capture_events()

with mock.patch(
"sentry_sdk.integrations.boto3.parse_url",
"sentry_sdk.integrations.boto3._instrumentation.parse_url",
side_effect=ValueError,
):
with sentry_sdk.start_transaction() as transaction, MockResponse(
Expand Down
Loading