Skip to content
Open
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
18 changes: 18 additions & 0 deletions sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ class SPANDATA:
Example: "10.1.2.80"
"""

CLOUD_REGION = "cloud.region"
"""
The geographical region the resource is running.
Example: "us-east-1"
"""

CODE_FILEPATH = "code.filepath"
"""
.. deprecated::
Expand Down Expand Up @@ -977,12 +983,24 @@ class SPANDATA:
Example: "com.example.ExampleService/exampleMethod"
"""

RPC_SERVICE = "rpc.service"
"""
The full (logical) name of the service being called, including its package name, if applicable.
Example: "myService.BestService"
"""

RPC_RESPONSE_STATUS_CODE = "rpc.response.status_code"
"""
Status code of the RPC returned by the RPC server or generated by the client.
Example: "DEADLINE_EXCEEDED"
"""

RPC_SYSTEM_NAME = "rpc.system.name"
"""
A string identifying the remoting system.
Example: "aws-api"
"""

Comment thread
pabloDeputter marked this conversation as resolved.
SERVER_ADDRESS = "server.address"
"""
Name of the database host.
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/boto3/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def sentry_patched_make_api_call(
if client.get_integration(Boto3Integration) is None:
return orig_make_api_call(self, operation_name, api_params)

ctx = AwsCallContext(operation_name)
ctx = AwsCallContext(operation_name, api_params)

# add optional metadata to context.
with capture_internal_exceptions():
Expand Down
22 changes: 20 additions & 2 deletions sentry_sdk/integrations/boto3/_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from sentry_sdk.utils import capture_internal_exceptions

if TYPE_CHECKING:
from typing import Any, Optional
from typing import Any, Dict, Optional

try:
from botocore.client import BaseClient
Expand All @@ -14,15 +14,27 @@

class AwsCallContext:
__slots__ = (
"service_name",
"service_id",
"service_id_hyphenized",
"operation_name",
"region_name",
"endpoint_url",
"params",
)

def __init__(self, operation_name: str) -> None:
def __init__(self, operation_name: str, params: "Any") -> None:
self.operation_name: str = operation_name
self.params: "Dict[str, Any]" = {}
self.service_name: "Optional[str]" = None
self.service_id: "Optional[str]" = None
self.service_id_hyphenized: "Optional[str]" = None
self.region_name: "Optional[str]" = None
self.endpoint_url: "Optional[str]" = None

if isinstance(params, dict):
with capture_internal_exceptions():
self.params = dict(params)

def add_metadata(self, client: "BaseClient") -> None:
def _get_attr(obj: "Any", name: str) -> "Any":
Expand All @@ -35,10 +47,16 @@ def _get_attr(obj: "Any", name: str) -> "Any":
client_meta = _get_attr(client, "meta")
service_model = _get_attr(client_meta, "service_model")

# botocore's internal identifier, e.g. `apigateway`.
self.service_name = _get_attr(service_model, "service_name")

# modeled AWS service identity used in span names, e.g. `API Gateway`.
service_id = _get_attr(service_model, "service_id")
if service_id is not None:
with capture_internal_exceptions():
self.service_id = str(service_id)
with capture_internal_exceptions():
self.service_id_hyphenized = service_id.hyphenize()

self.region_name = _get_attr(client_meta, "region_name")
self.endpoint_url = _get_attr(client_meta, "endpoint_url")
101 changes: 83 additions & 18 deletions sentry_sdk/integrations/boto3/_instrumentation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import TYPE_CHECKING
from urllib.parse import urlsplit

import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
Expand Down Expand Up @@ -31,30 +32,95 @@
raise DidNotEnable("botocore not installed")


_AWS_RPC_SYSTEM_NAME = "aws-api"


def _set_span_attributes(
span: "Union[Span, StreamedSpan]", attributes: "Attributes"
) -> None:
if isinstance(span, StreamedSpan):
span.set_attributes(attributes)
return

for key, value in attributes.items():
span.set_data(key, value)


def _get_server_attributes(endpoint_url: "Optional[str]") -> "Attributes":
if not endpoint_url:
return {}

default_ports = {
"http": 80,
"https": 443,
}

try:
parsed_url = urlsplit(endpoint_url)
Comment thread
alexander-alderman-webb marked this conversation as resolved.
if parsed_url.scheme not in default_ports or not parsed_url.hostname:
return {}

# `server.port` is only defined together with `server.address`.
# Infer the effective port when the configured HTTP(S) endpoint omits it.
# https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/
return {
SPANDATA.SERVER_ADDRESS: parsed_url.hostname,
SPANDATA.SERVER_PORT: parsed_url.port or default_ports[parsed_url.scheme],
}

except (TypeError, UnicodeError, ValueError):
# Invalid client metadata must not prevent the AWS call from running.
return {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong server address on AWS spans

Medium Severity

server.address and server.port are taken from the client's configured endpoint_url and never updated from the resolved request URL. S3 virtual-hosted calls therefore record the base endpoint, such as s3.amazonaws.com, instead of the host that was contacted, such as bucket.s3.amazonaws.com. On streamed spans without PII, url.full is absent, so nothing else shows the real host.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 64d7159. Configure here.



def _get_client_attributes(
ctx: "AwsCallContext",
) -> "Attributes":
attributes: "Attributes" = {}

# `rpc.service` is deprecated in OTel, but js still uses it.
if ctx.service_id:
attributes[SPANDATA.RPC_SERVICE] = ctx.service_id

if ctx.region_name:
attributes[SPANDATA.CLOUD_REGION] = ctx.region_name

attributes.update(_get_server_attributes(ctx.endpoint_url))
return attributes


def _start_client_span(
ctx: "AwsCallContext",
) -> "Optional[Union[Span, StreamedSpan]]":
from sentry_sdk.integrations.boto3 import Boto3Integration

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

# use unknown if `service_id_hyphenized` so span name can still be created.
# e.g. "aws.unkown.GetObject"
service_name = ctx.service_id_hyphenized or "unknown"
span_name = f"aws.{service_name}.{ctx.operation_name}"
attributes: "Attributes" = {
SPANDATA.RPC_METHOD: ctx.operation_name,
SPANDATA.RPC_SYSTEM_NAME: _AWS_RPC_SYSTEM_NAME,
}
with capture_internal_exceptions():
attributes.update(_get_client_attributes(ctx))
span_op = OP.HTTP_CLIENT
span_origin = ORIGIN

if has_span_streaming_enabled(client.options):
if sentry_sdk.traces.get_current_span() is None:
return None

attributes: "Attributes" = {
SPANDATA.SENTRY_OP: OP.HTTP_CLIENT,
SPANDATA.SENTRY_ORIGIN: ORIGIN,
}
if ctx.service_id:
attributes[SPANDATA.RPC_METHOD] = f"{ctx.service_id}/{ctx.operation_name}"
# `start_span()` evaluates `ignore_spans` against the initial attributes.
# https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#rpc-client-span
attributes.update(
{
SPANDATA.SENTRY_OP: span_op,
SPANDATA.SENTRY_ORIGIN: span_origin,
}
)
return sentry_sdk.traces.start_span(
name=span_name,
attributes=attributes,
Expand All @@ -65,9 +131,11 @@ def _start_client_span(

span = sentry_sdk.start_span(
name=span_name,
op=OP.HTTP_CLIENT,
origin=ORIGIN,
op=span_op,
origin=span_origin,
)
with capture_internal_exceptions():
_set_span_attributes(span, attributes)
with capture_internal_exceptions():
if ctx.service_id_hyphenized:
span.set_tag("aws.service_id", ctx.service_id_hyphenized)
Expand Down Expand Up @@ -113,8 +181,8 @@ def _instrument_streaming_body(
# unrelated new spans attach to the stream span since it's the current span.
active=False,
attributes={
"sentry.op": OP.HTTP_CLIENT_STREAM,
"sentry.origin": ORIGIN,
SPANDATA.SENTRY_OP: OP.HTTP_CLIENT_STREAM,
SPANDATA.SENTRY_ORIGIN: ORIGIN,
},
)
else:
Expand Down Expand Up @@ -265,10 +333,9 @@ def _sentry_request_created(
fresh `AWSRequest` on every retry.
https://github.com/boto/botocore/blob/f9195c79ea2bf46350dd320d2a0bf3db7da0b460/botocore/endpoint.py#L178-L202
"""
from sentry_sdk.integrations.boto3 import Boto3Integration

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

with capture_internal_exceptions():
Expand All @@ -295,10 +362,8 @@ def _sentry_request_created(
def _sentry_before_sign(
request: "AWSRequest", signature_version: "Any", **kwargs: "Any"
) -> None:
from sentry_sdk.integrations.boto3 import Boto3Integration

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

with capture_internal_exceptions():
Expand Down
Loading
Loading