77from sentry_sdk .scope import should_send_default_pii
88from sentry_sdk .traces import StreamedSpan
99from sentry_sdk .tracing import Span
10- from sentry_sdk .tracing_utils import has_span_streaming_enabled
10+ from sentry_sdk .tracing_utils import add_http_breadcrumb , has_span_streaming_enabled
1111from sentry_sdk .utils import (
1212 capture_internal_exceptions ,
1313 parse_url ,
@@ -64,28 +64,48 @@ def _sentry_request_created(
6464 if client .get_integration (Boto3Integration ) is None :
6565 return
6666
67+ parsed_url = None
68+ if request .url is not None :
69+ with capture_internal_exceptions ():
70+ parsed_url = parse_url (request .url , sanitize = False )
71+
72+ breadcrumb : "dict[str, Any]" = {}
73+
6774 is_span_streaming_enabled = has_span_streaming_enabled (client .options )
6875 span : "Union[Span, StreamedSpan]"
6976 if is_span_streaming_enabled :
70- if sentry_sdk .traces .get_current_span () is None :
71- return
72- span = sentry_sdk .traces .start_span (
73- name = description ,
74- attributes = {
75- "sentry.op" : OP .HTTP_CLIENT ,
76- "sentry.origin" : Boto3Integration .origin ,
77- SPANDATA .RPC_METHOD : f"{ service_id } /{ operation_name } " ,
78- },
79- )
80- if request .url is not None and should_send_default_pii ():
81- with capture_internal_exceptions ():
82- parsed_url = parse_url (request .url , sanitize = False )
83- span .set_attribute (SPANDATA .URL_FULL , parsed_url .url )
84- span .set_attribute (SPANDATA .URL_QUERY , parsed_url .query )
85- span .set_attribute (SPANDATA .URL_FRAGMENT , parsed_url .fragment )
77+ if parsed_url and should_send_default_pii ():
78+ breadcrumb .update (
79+ {
80+ SPANDATA .URL_FULL : parsed_url .url ,
81+ SPANDATA .URL_QUERY : parsed_url .query ,
82+ SPANDATA .URL_FRAGMENT : parsed_url .fragment ,
83+ }
84+ )
8685
8786 if request .method is not None :
88- span .set_attribute (SPANDATA .HTTP_REQUEST_METHOD , request .method )
87+ breadcrumb [SPANDATA .HTTP_REQUEST_METHOD ] = request .method
88+
89+ if sentry_sdk .traces .get_current_span () is not None :
90+ span = sentry_sdk .traces .start_span (
91+ name = description ,
92+ attributes = {
93+ "sentry.op" : OP .HTTP_CLIENT ,
94+ "sentry.origin" : Boto3Integration .origin ,
95+ SPANDATA .RPC_METHOD : f"{ service_id } /{ operation_name } " ,
96+ },
97+ )
98+ if parsed_url and should_send_default_pii ():
99+ span .set_attributes (
100+ {
101+ SPANDATA .URL_FULL : parsed_url .url ,
102+ SPANDATA .URL_QUERY : parsed_url .query ,
103+ SPANDATA .URL_FRAGMENT : parsed_url .fragment ,
104+ }
105+ )
106+
107+ if request .method is not None :
108+ span .set_attribute (SPANDATA .HTTP_REQUEST_METHOD , request .method )
89109 else :
90110 span = sentry_sdk .start_span (
91111 op = OP .HTTP_CLIENT ,
@@ -95,15 +115,22 @@ def _sentry_request_created(
95115
96116 if request .url is not None :
97117 with capture_internal_exceptions ():
98- parsed_url = parse_url (request .url , sanitize = False )
99118 span .set_data ("aws.request.url" , parsed_url .url )
100119 span .set_data (SPANDATA .HTTP_QUERY , parsed_url .query )
101120 span .set_data (SPANDATA .HTTP_FRAGMENT , parsed_url .fragment )
121+ breadcrumb .update (
122+ {
123+ "aws.request.url" : parsed_url .url ,
124+ SPANDATA .HTTP_QUERY : parsed_url .query ,
125+ SPANDATA .HTTP_FRAGMENT : parsed_url .fragment ,
126+ }
127+ )
102128
103129 span .set_tag ("aws.service_id" , service_id .hyphenize ())
104130 span .set_tag ("aws.operation_name" , operation_name )
105131 if request .method is not None :
106132 span .set_data (SPANDATA .HTTP_METHOD , request .method )
133+ breadcrumb [SPANDATA .HTTP_METHOD ] = request .method
107134
108135 # We do it in order for subsequent http calls/retries be
109136 # attached to this span.
@@ -112,17 +139,22 @@ def _sentry_request_created(
112139 # request.context is an open-ended data-structure
113140 # where we can add anything useful in request life cycle.
114141 request .context ["_sentrysdk_span" ] = span
142+ request .context ["_sentrysdk_breadcrumb" ] = breadcrumb
115143
116144
117145def _sentry_after_call (
118146 context : "Dict[str, Any]" , parsed : "Dict[str, Any]" , ** kwargs : "Any"
119147) -> None :
120148 span : "Optional[Union[Span, StreamedSpan]]" = context .pop ("_sentrysdk_span" , None )
149+ breadcrumb : "Optional[dict[str, Any]]" = context .pop ("_sentrysdk_breadcrumb" , None )
121150
122151 # Span could be absent if the integration is disabled.
123152 if span is None :
124153 return
154+
125155 span .__exit__ (None , None , None )
156+ if breadcrumb :
157+ add_http_breadcrumb (None , breadcrumb )
126158
127159 body = parsed .get ("Body" )
128160 if not isinstance (body , StreamingBody ):
@@ -182,8 +214,11 @@ def _sentry_after_call_error(
182214 context : "Dict[str, Any]" , exception : "Type[BaseException]" , ** kwargs : "Any"
183215) -> None :
184216 span : "Optional[Union[Span, StreamedSpan]]" = context .pop ("_sentrysdk_span" , None )
217+ breadcrumb : "Optional[dict[str, Any]]" = context .pop ("_sentrysdk_breadcrumb" , None )
185218
186219 # Span could be absent if the integration is disabled.
187220 if span is None :
188221 return
222+
223+ add_http_breadcrumb (None , breadcrumb )
189224 span .__exit__ (type (exception ), exception , None )
0 commit comments