44
55import sentry_sdk
66from sentry_sdk .api import continue_trace
7- from sentry_sdk .consts import OP
7+ from sentry_sdk .consts import OP , SPANDATA
88from sentry_sdk .scope import should_send_default_pii
9+ from sentry_sdk .traces import SegmentSource , StreamedSpan
910from sentry_sdk .tracing import TransactionSource
11+ from sentry_sdk .tracing_utils import has_span_streaming_enabled
1012from sentry_sdk .utils import (
1113 HAS_REAL_CONTEXTVARS ,
1214 CONTEXTVARS_ERROR_MESSAGE ,
3436
3537if TYPE_CHECKING :
3638 from typing import Any
39+ from typing import ContextManager
3740 from typing import Optional
3841 from typing import Dict
3942 from typing import Callable
4043 from typing import Generator
44+ from typing import Union
45+
46+ from sentry_sdk .tracing import Span
4147
4248 from sentry_sdk ._types import Event , EventProcessor
4349
@@ -101,6 +107,9 @@ def sentry_log_exception(
101107 RequestHandler .log_exception = sentry_log_exception
102108
103109
110+ _DEFAULT_TRANSACTION_NAME = "generic Tornado request"
111+
112+
104113@contextlib .contextmanager
105114def _handle_request_impl (self : "RequestHandler" ) -> "Generator[None, None, None]" :
106115 integration = sentry_sdk .get_client ().get_integration (TornadoIntegration )
@@ -110,6 +119,8 @@ def _handle_request_impl(self: "RequestHandler") -> "Generator[None, None, None]
110119 return
111120
112121 weak_handler = weakref .ref (self )
122+ client = sentry_sdk .get_client ()
123+ span_streaming = has_span_streaming_enabled (client .options )
113124
114125 with sentry_sdk .isolation_scope () as scope :
115126 headers = self .request .headers
@@ -118,22 +129,90 @@ def _handle_request_impl(self: "RequestHandler") -> "Generator[None, None, None]
118129 processor = _make_event_processor (weak_handler )
119130 scope .add_event_processor (processor )
120131
121- transaction = continue_trace (
122- headers ,
123- op = OP .HTTP_SERVER ,
124- # Like with all other integrations, this is our
125- # fallback transaction in case there is no route.
126- # sentry_urldispatcher_resolve is responsible for
127- # setting a transaction name later.
128- name = "generic Tornado request" ,
129- source = TransactionSource .ROUTE ,
130- origin = TornadoIntegration .origin ,
131- )
132-
133- with sentry_sdk .start_transaction (
134- transaction , custom_sampling_context = {"tornado_request" : self .request }
135- ):
136- yield
132+ span_ctx : "ContextManager[Union[Span, StreamedSpan, None]]"
133+
134+ if span_streaming :
135+ sentry_sdk .traces .continue_trace (dict (headers ))
136+ scope .set_custom_sampling_context ({"tornado_request" : self .request })
137+
138+ span_ctx = sentry_sdk .traces .start_span (
139+ name = _DEFAULT_TRANSACTION_NAME ,
140+ attributes = {
141+ "sentry.op" : OP .HTTP_SERVER ,
142+ "sentry.origin" : TornadoIntegration .origin ,
143+ "sentry.span.source" : SegmentSource .ROUTE ,
144+ },
145+ )
146+ else :
147+ transaction = continue_trace (
148+ headers ,
149+ op = OP .HTTP_SERVER ,
150+ # Like with all other integrations, this is our
151+ # fallback transaction in case there is no route.
152+ # sentry_urldispatcher_resolve is responsible for
153+ # setting a transaction name later.
154+ name = _DEFAULT_TRANSACTION_NAME ,
155+ source = TransactionSource .ROUTE ,
156+ origin = TornadoIntegration .origin ,
157+ )
158+ span_ctx = sentry_sdk .start_transaction (
159+ transaction ,
160+ custom_sampling_context = {"tornado_request" : self .request },
161+ )
162+
163+ with span_ctx as span :
164+ if isinstance (span , StreamedSpan ):
165+ with capture_internal_exceptions ():
166+ for attr , value in _get_request_attributes (self .request ).items ():
167+ span .set_attribute (attr , value )
168+
169+ method = getattr (self , self .request .method .lower (), None )
170+ if method is not None :
171+ tx_name = transaction_from_function (method ) or ""
172+ if tx_name :
173+ span .name = tx_name
174+ span .set_attribute (
175+ "sentry.span.source" ,
176+ SegmentSource .COMPONENT .value ,
177+ )
178+
179+ try :
180+ yield
181+ finally :
182+ if isinstance (span , StreamedSpan ):
183+ with capture_internal_exceptions ():
184+ status_int = self .get_status ()
185+ span .set_attribute (SPANDATA .HTTP_STATUS_CODE , status_int )
186+ span .status = "error" if status_int >= 400 else "ok"
187+
188+
189+ def _get_request_attributes (request : "Any" ) -> "Dict[str, Any]" :
190+ attributes = {} # type: Dict[str, Any]
191+
192+ if request .method :
193+ attributes [SPANDATA .HTTP_REQUEST_METHOD ] = request .method .upper ()
194+
195+ headers = _filter_headers (dict (request .headers ), use_annotated_value = False )
196+ for header , value in headers .items ():
197+ attributes [f"http.request.header.{ header .lower ()} " ] = value
198+
199+ if request .query :
200+ attributes [SPANDATA .HTTP_QUERY ] = request .query
201+
202+ attributes [SPANDATA .URL_FULL ] = "%s://%s%s" % (
203+ request .protocol ,
204+ request .host ,
205+ request .path ,
206+ )
207+
208+ if request .protocol :
209+ attributes ["network.protocol.name" ] = request .protocol
210+
211+ if should_send_default_pii () and request .remote_ip :
212+ attributes ["client.address" ] = request .remote_ip
213+ attributes ["user.ip_address" ] = request .remote_ip
214+
215+ return attributes
137216
138217
139218@ensure_integration_enabled (TornadoIntegration )
0 commit comments