11from typing import TYPE_CHECKING
2+ from urllib .parse import urlsplit
23
34import sentry_sdk
45from sentry_sdk .consts import OP , SPANDATA , SPANSTATUS
3132 raise DidNotEnable ("botocore not installed" )
3233
3334
35+ _AWS_RPC_SYSTEM_NAME = "aws-api"
36+
37+
38+ def _set_span_attributes (
39+ span : "Union[Span, StreamedSpan]" , attributes : "Attributes"
40+ ) -> None :
41+ if isinstance (span , StreamedSpan ):
42+ span .set_attributes (attributes )
43+ return
44+
45+ for key , value in attributes .items ():
46+ span .set_data (key , value )
47+
48+
49+ def _get_server_attributes (endpoint_url : "Optional[str]" ) -> "Attributes" :
50+ if not endpoint_url :
51+ return {}
52+
53+ default_ports = {
54+ "http" : 80 ,
55+ "https" : 443 ,
56+ }
57+
58+ try :
59+ parsed_url = urlsplit (endpoint_url )
60+ if parsed_url .scheme not in default_ports or not parsed_url .hostname :
61+ return {}
62+
63+ # `server.port` is only defined together with `server.address`.
64+ # Infer the effective port when the configured HTTP(S) endpoint omits it.
65+ # https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/
66+ return {
67+ SPANDATA .SERVER_ADDRESS : parsed_url .hostname ,
68+ SPANDATA .SERVER_PORT : parsed_url .port or default_ports [parsed_url .scheme ],
69+ }
70+
71+ except (TypeError , UnicodeError , ValueError ):
72+ # Invalid client metadata must not prevent the AWS call from running.
73+ return {}
74+
75+
76+ def _get_client_attributes (
77+ ctx : "AwsCallContext" ,
78+ ) -> "Attributes" :
79+ attributes : "Attributes" = {}
80+
81+ # `rpc.service` is deprecated in OTel, but js still uses it.
82+ if ctx .service_id :
83+ attributes [SPANDATA .RPC_SERVICE ] = ctx .service_id
84+
85+ if ctx .region_name :
86+ attributes [SPANDATA .CLOUD_REGION ] = ctx .region_name
87+
88+ attributes .update (_get_server_attributes (ctx .endpoint_url ))
89+ return attributes
90+
91+
3492def _start_client_span (
3593 ctx : "AwsCallContext" ,
3694) -> "Optional[Union[Span, StreamedSpan]]" :
37- from sentry_sdk .integrations .boto3 import Boto3Integration
38-
3995 client = sentry_sdk .get_client ()
40- if client .get_integration (Boto3Integration ) is None :
96+ if client .get_integration ("boto3" ) is None :
4197 return None
4298
4399 # use unknown if `service_id_hyphenized` so span name can still be created.
44100 # e.g. "aws.unkown.GetObject"
45101 service_name = ctx .service_id_hyphenized or "unknown"
46102 span_name = f"aws.{ service_name } .{ ctx .operation_name } "
103+ attributes : "Attributes" = {
104+ SPANDATA .RPC_METHOD : ctx .operation_name ,
105+ SPANDATA .RPC_SYSTEM_NAME : _AWS_RPC_SYSTEM_NAME ,
106+ }
107+ with capture_internal_exceptions ():
108+ attributes .update (_get_client_attributes (ctx ))
109+ span_op = OP .HTTP_CLIENT
110+ span_origin = ORIGIN
47111
48112 if has_span_streaming_enabled (client .options ):
49113 if sentry_sdk .traces .get_current_span () is None :
50114 return None
51115
52- attributes : "Attributes" = {
53- SPANDATA .SENTRY_OP : OP .HTTP_CLIENT ,
54- SPANDATA .SENTRY_ORIGIN : ORIGIN ,
55- }
56- if ctx .service_id :
57- attributes [SPANDATA .RPC_METHOD ] = f"{ ctx .service_id } /{ ctx .operation_name } "
116+ # `start_span()` evaluates `ignore_spans` against the initial attributes.
117+ # https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#rpc-client-span
118+ attributes .update (
119+ {
120+ SPANDATA .SENTRY_OP : span_op ,
121+ SPANDATA .SENTRY_ORIGIN : span_origin ,
122+ }
123+ )
58124 return sentry_sdk .traces .start_span (
59125 name = span_name ,
60126 attributes = attributes ,
@@ -65,9 +131,11 @@ def _start_client_span(
65131
66132 span = sentry_sdk .start_span (
67133 name = span_name ,
68- op = OP . HTTP_CLIENT ,
69- origin = ORIGIN ,
134+ op = span_op ,
135+ origin = span_origin ,
70136 )
137+ with capture_internal_exceptions ():
138+ _set_span_attributes (span , attributes )
71139 with capture_internal_exceptions ():
72140 if ctx .service_id_hyphenized :
73141 span .set_tag ("aws.service_id" , ctx .service_id_hyphenized )
@@ -113,8 +181,8 @@ def _instrument_streaming_body(
113181 # unrelated new spans attach to the stream span since it's the current span.
114182 active = False ,
115183 attributes = {
116- "sentry.op" : OP .HTTP_CLIENT_STREAM ,
117- "sentry.origin" : ORIGIN ,
184+ SPANDATA . SENTRY_OP : OP .HTTP_CLIENT_STREAM ,
185+ SPANDATA . SENTRY_ORIGIN : ORIGIN ,
118186 },
119187 )
120188 else :
@@ -265,10 +333,9 @@ def _sentry_request_created(
265333 fresh `AWSRequest` on every retry.
266334 https://github.com/boto/botocore/blob/f9195c79ea2bf46350dd320d2a0bf3db7da0b460/botocore/endpoint.py#L178-L202
267335 """
268- from sentry_sdk .integrations .boto3 import Boto3Integration
269336
270337 client = sentry_sdk .get_client ()
271- if client .get_integration (Boto3Integration ) is None :
338+ if client .get_integration ("boto3" ) is None :
272339 return
273340
274341 with capture_internal_exceptions ():
@@ -295,10 +362,8 @@ def _sentry_request_created(
295362def _sentry_before_sign (
296363 request : "AWSRequest" , signature_version : "Any" , ** kwargs : "Any"
297364) -> None :
298- from sentry_sdk .integrations .boto3 import Boto3Integration
299-
300365 client = sentry_sdk .get_client ()
301- if client .get_integration (Boto3Integration ) is None :
366+ if client .get_integration ("boto3" ) is None :
302367 return
303368
304369 with capture_internal_exceptions ():
0 commit comments