66
77import sentry_sdk
88from sentry_sdk import continue_trace
9- from sentry_sdk .consts import OP
9+ from sentry_sdk .consts import OP , SPANDATA
1010from sentry_sdk .integrations import DidNotEnable , Integration , _check_minimum_version
1111from sentry_sdk .integrations ._wsgi_common import RequestExtractor , _filter_headers
1212from sentry_sdk .integrations .logging import ignore_logger
13+ from sentry_sdk .scope import should_send_default_pii
14+ from sentry_sdk .traces import SegmentSource , StreamedSpan
1315from sentry_sdk .tracing import TransactionSource
16+ from sentry_sdk .tracing_utils import has_span_streaming_enabled
1417from sentry_sdk .utils import (
1518 CONTEXTVARS_ERROR_MESSAGE ,
1619 HAS_REAL_CONTEXTVARS ,
@@ -165,23 +168,43 @@ async def _context_enter(request: "Request") -> None:
165168 if not request .ctx ._sentry_do_integration :
166169 return
167170
171+ client = sentry_sdk .get_client ()
172+ is_span_streaming_enabled = has_span_streaming_enabled (client .options )
173+
168174 weak_request = weakref .ref (request )
169175 request .ctx ._sentry_scope = sentry_sdk .isolation_scope ()
170176 scope = request .ctx ._sentry_scope .__enter__ ()
171177 scope .clear_breadcrumbs ()
172178 scope .add_event_processor (_make_request_processor (weak_request ))
173179
174- transaction = continue_trace (
175- dict (request .headers ),
176- op = OP .HTTP_SERVER ,
177- # Unless the request results in a 404 error, the name and source will get overwritten in _set_transaction
178- name = request .path ,
179- source = TransactionSource .URL ,
180- origin = SanicIntegration .origin ,
181- )
182- request .ctx ._sentry_transaction = sentry_sdk .start_transaction (
183- transaction
184- ).__enter__ ()
180+ if is_span_streaming_enabled :
181+ sentry_sdk .traces .continue_trace (dict (request .headers ))
182+ scope .set_custom_sampling_context ({"sanic_request" : request })
183+
184+ span = sentry_sdk .traces .start_span (
185+ # Unless the request results in a 404 error, the name and source
186+ # will get overwritten in _set_transaction
187+ name = request .path ,
188+ attributes = {
189+ "sentry.op" : OP .HTTP_SERVER ,
190+ "sentry.origin" : SanicIntegration .origin ,
191+ "sentry.span.source" : SegmentSource .URL .value ,
192+ },
193+ parent_span = None ,
194+ )
195+ request .ctx ._sentry_root_span = span
196+ else :
197+ transaction = continue_trace (
198+ dict (request .headers ),
199+ op = OP .HTTP_SERVER ,
200+ # Unless the request results in a 404 error, the name and source will get overwritten in _set_transaction
201+ name = request .path ,
202+ source = TransactionSource .URL ,
203+ origin = SanicIntegration .origin ,
204+ )
205+ request .ctx ._sentry_root_span = sentry_sdk .start_transaction (
206+ transaction
207+ ).__enter__ ()
185208
186209
187210async def _context_exit (
@@ -194,16 +217,31 @@ async def _context_exit(
194217 integration = sentry_sdk .get_client ().get_integration (SanicIntegration )
195218
196219 response_status = None if response is None else response .status
220+ should_sample = (
221+ isinstance (integration , SanicIntegration )
222+ and response_status not in integration ._unsampled_statuses
223+ )
197224
198225 # This capture_internal_exceptions block has been intentionally nested here, so that in case an exception
199226 # happens while trying to end the transaction, we still attempt to exit the hub.
200227 with capture_internal_exceptions ():
201- request .ctx ._sentry_transaction .set_http_status (response_status )
202- request .ctx ._sentry_transaction .sampled &= (
203- isinstance (integration , SanicIntegration )
204- and response_status not in integration ._unsampled_statuses
205- )
206- request .ctx ._sentry_transaction .__exit__ (None , None , None )
228+ span = request .ctx ._sentry_root_span
229+ if isinstance (span , StreamedSpan ):
230+ with capture_internal_exceptions ():
231+ for attr , value in _get_request_attributes (request ).items ():
232+ span .set_attribute (attr , value )
233+ if response_status is not None :
234+ span .set_attribute (SPANDATA .HTTP_STATUS_CODE , response_status )
235+ span .status = "error" if response_status >= 400 else "ok"
236+ # If the status is in unsampled_statuses, skip ending the span so
237+ # it is never queued for sending. The orphaned scope is discarded
238+ # when the isolation scope exits below.
239+ if should_sample :
240+ span .__exit__ (None , None , None )
241+ else :
242+ span .set_http_status (response_status )
243+ span .sampled &= should_sample
244+ span .__exit__ (None , None , None )
207245
208246 request .ctx ._sentry_scope .__exit__ (None , None , None )
209247
@@ -315,6 +353,40 @@ def _capture_exception(exception: "Union[ExcInfo, BaseException]") -> None:
315353 sentry_sdk .capture_event (event , hint = hint )
316354
317355
356+ def _get_request_attributes (request : "Request" ) -> "Dict[str, Any]" :
357+ """
358+ Return span attributes related to the HTTP request from a Sanic request.
359+ """
360+ attributes = {} # type: Dict[str, Any]
361+
362+ if request .method :
363+ attributes [SPANDATA .HTTP_REQUEST_METHOD ] = request .method .upper ()
364+
365+ headers = _filter_headers (dict (request .headers ), use_annotated_value = False )
366+ for header , value in headers .items ():
367+ attributes [f"{ SPANDATA .HTTP_REQUEST_HEADER } .{ header .lower ()} " ] = value
368+
369+ urlparts = urlsplit (request .url )
370+
371+ if urlparts .query :
372+ attributes [SPANDATA .HTTP_QUERY ] = urlparts .query
373+
374+ attributes [SPANDATA .URL_FULL ] = "%s://%s%s" % (
375+ urlparts .scheme ,
376+ urlparts .netloc ,
377+ urlparts .path ,
378+ )
379+
380+ if urlparts .scheme :
381+ attributes [SPANDATA .NETWORK_PROTOCOL_NAME ] = urlparts .scheme
382+
383+ if should_send_default_pii () and request .remote_addr :
384+ attributes [SPANDATA .CLIENT_ADDRESS ] = request .remote_addr
385+ attributes [SPANDATA .USER_IP_ADDRESS ] = request .remote_addr
386+
387+ return attributes
388+
389+
318390def _make_request_processor (weak_request : "Callable[[], Request]" ) -> "EventProcessor" :
319391 def sanic_processor (event : "Event" , hint : "Optional[Hint]" ) -> "Optional[Event]" :
320392 try :
0 commit comments