44from sentry_sdk .consts import OP , SPANDATA
55from sentry_sdk .integrations import _check_minimum_version , Integration , DidNotEnable
66from sentry_sdk .tracing import Span
7+ from sentry_sdk .traces import StreamedSpan
8+ from sentry_sdk .tracing_utils import has_span_streaming_enabled
79from sentry_sdk .utils import (
810 capture_internal_exceptions ,
9- ensure_integration_enabled ,
1011 parse_url ,
1112 parse_version ,
1213)
1819 from typing import Dict
1920 from typing import Optional
2021 from typing import Type
22+ from typing import Union
23+
24+ from botocore .model import ServiceId
2125
2226try :
2327 from botocore import __version__ as BOTOCORE_VERSION
@@ -44,7 +48,7 @@ def sentry_patched_init(
4448 ) -> None :
4549 orig_init (self , * args , ** kwargs )
4650 meta = self .meta
47- service_id = meta .service_model .service_id . hyphenize ()
51+ service_id = meta .service_model .service_id
4852 meta .events .register (
4953 "request-created" ,
5054 partial (_sentry_request_created , service_id = service_id ),
@@ -55,27 +59,52 @@ def sentry_patched_init(
5559 BaseClient .__init__ = sentry_patched_init # type: ignore
5660
5761
58- @ensure_integration_enabled (Boto3Integration )
5962def _sentry_request_created (
60- service_id : str , request : "AWSRequest" , operation_name : str , ** kwargs : "Any"
63+ service_id : "ServiceId" , request : "AWSRequest" , operation_name : str , ** kwargs : "Any"
6164) -> None :
62- description = "aws.%s.%s" % (service_id , operation_name )
63- span = sentry_sdk .start_span (
64- op = OP .HTTP_CLIENT ,
65- name = description ,
66- origin = Boto3Integration .origin ,
67- )
68-
69- if request .url is not None :
70- with capture_internal_exceptions ():
71- parsed_url = parse_url (request .url , sanitize = False )
72- span .set_data ("aws.request.url" , parsed_url .url )
73- span .set_data (SPANDATA .HTTP_QUERY , parsed_url .query )
74- span .set_data (SPANDATA .HTTP_FRAGMENT , parsed_url .fragment )
75-
76- span .set_tag ("aws.service_id" , service_id )
77- span .set_tag ("aws.operation_name" , operation_name )
78- span .set_data (SPANDATA .HTTP_METHOD , request .method )
65+ description = "aws.%s.%s" % (service_id .hyphenize (), operation_name )
66+
67+ client = sentry_sdk .get_client ()
68+ if client .get_integration (Boto3Integration ) is None :
69+ return
70+
71+ is_span_streaming_enabled = has_span_streaming_enabled (client .options )
72+ span : "Union[Span, StreamedSpan]"
73+ if is_span_streaming_enabled :
74+ span = sentry_sdk .traces .start_span (
75+ name = description ,
76+ attributes = {
77+ "sentry.op" : OP .HTTP_CLIENT ,
78+ "sentry.origin" : Boto3Integration .origin ,
79+ SPANDATA .RPC_METHOD : f"{ service_id } /{ operation_name } " ,
80+ },
81+ )
82+ if request .url is not None :
83+ with capture_internal_exceptions ():
84+ parsed_url = parse_url (request .url , sanitize = False )
85+ span .set_attribute (SPANDATA .URL_FULL , parsed_url .url )
86+ span .set_attribute (SPANDATA .URL_QUERY , parsed_url .query )
87+ span .set_attribute (SPANDATA .URL_FRAGMENT , parsed_url .fragment )
88+
89+ if request .method is not None :
90+ span .set_attribute (SPANDATA .HTTP_REQUEST_METHOD , request .method )
91+ else :
92+ span = sentry_sdk .start_span (
93+ op = OP .HTTP_CLIENT ,
94+ name = description ,
95+ origin = Boto3Integration .origin ,
96+ )
97+
98+ if request .url is not None :
99+ with capture_internal_exceptions ():
100+ parsed_url = parse_url (request .url , sanitize = False )
101+ span .set_data ("aws.request.url" , parsed_url .url )
102+ span .set_data (SPANDATA .HTTP_QUERY , parsed_url .query )
103+ span .set_data (SPANDATA .HTTP_FRAGMENT , parsed_url .fragment )
104+
105+ span .set_tag ("aws.service_id" , service_id .hyphenize ())
106+ span .set_tag ("aws.operation_name" , operation_name )
107+ span .set_data (SPANDATA .HTTP_METHOD , request .method )
79108
80109 # We do it in order for subsequent http calls/retries be
81110 # attached to this span.
@@ -89,7 +118,7 @@ def _sentry_request_created(
89118def _sentry_after_call (
90119 context : "Dict[str, Any]" , parsed : "Dict[str, Any]" , ** kwargs : "Any"
91120) -> None :
92- span : "Optional[Span]" = context .pop ("_sentrysdk_span" , None )
121+ span : "Optional[Union[ Span, StreamedSpan] ]" = context .pop ("_sentrysdk_span" , None )
93122
94123 # Span could be absent if the integration is disabled.
95124 if span is None :
@@ -100,29 +129,51 @@ def _sentry_after_call(
100129 if not isinstance (body , StreamingBody ):
101130 return
102131
103- streaming_span = span .start_child (
104- op = OP .HTTP_CLIENT_STREAM ,
105- name = span .description ,
106- origin = Boto3Integration .origin ,
107- )
132+ streaming_span : "Union[Span, StreamedSpan]"
133+ if isinstance (span , StreamedSpan ):
134+ streaming_span = sentry_sdk .traces .start_span (
135+ name = span .name ,
136+ parent_span = span ,
137+ attributes = {
138+ "sentry.op" : OP .HTTP_CLIENT_STREAM ,
139+ "sentry.origin" : Boto3Integration .origin ,
140+ },
141+ )
142+ else :
143+ streaming_span = span .start_child (
144+ op = OP .HTTP_CLIENT_STREAM ,
145+ name = span .description ,
146+ origin = Boto3Integration .origin ,
147+ )
108148
109149 orig_read = body .read
110150 orig_close = body .close
111151
112152 def sentry_streaming_body_read (* args : "Any" , ** kwargs : "Any" ) -> bytes :
113153 try :
114154 ret = orig_read (* args , ** kwargs )
115- if not ret :
155+ if ret :
156+ return ret
157+
158+ if isinstance (streaming_span , StreamedSpan ):
159+ streaming_span .end ()
160+ else :
116161 streaming_span .finish ()
117162 return ret
118163 except Exception :
119- streaming_span .finish ()
164+ if isinstance (streaming_span , StreamedSpan ):
165+ streaming_span .end ()
166+ else :
167+ streaming_span .finish ()
120168 raise
121169
122170 body .read = sentry_streaming_body_read # type: ignore
123171
124172 def sentry_streaming_body_close (* args : "Any" , ** kwargs : "Any" ) -> None :
125- streaming_span .finish ()
173+ if isinstance (streaming_span , StreamedSpan ):
174+ streaming_span .end ()
175+ else :
176+ streaming_span .finish ()
126177 orig_close (* args , ** kwargs )
127178
128179 body .close = sentry_streaming_body_close # type: ignore
@@ -131,7 +182,7 @@ def sentry_streaming_body_close(*args: "Any", **kwargs: "Any") -> None:
131182def _sentry_after_call_error (
132183 context : "Dict[str, Any]" , exception : "Type[BaseException]" , ** kwargs : "Any"
133184) -> None :
134- span : "Optional[Span]" = context .pop ("_sentrysdk_span" , None )
185+ span : "Optional[Union[ Span, StreamedSpan] ]" = context .pop ("_sentrysdk_span" , None )
135186
136187 # Span could be absent if the integration is disabled.
137188 if span is None :
0 commit comments