-
Notifications
You must be signed in to change notification settings - Fork 610
feat(tornado): Support span streaming #6206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,9 +4,11 @@ | |||||||||||||
|
|
||||||||||||||
| import sentry_sdk | ||||||||||||||
| from sentry_sdk.api import continue_trace | ||||||||||||||
| from sentry_sdk.consts import OP | ||||||||||||||
| from sentry_sdk.consts import OP, SPANDATA | ||||||||||||||
| from sentry_sdk.scope import should_send_default_pii | ||||||||||||||
| from sentry_sdk.traces import SegmentSource, StreamedSpan | ||||||||||||||
| from sentry_sdk.tracing import TransactionSource | ||||||||||||||
| from sentry_sdk.tracing_utils import has_span_streaming_enabled | ||||||||||||||
| from sentry_sdk.utils import ( | ||||||||||||||
| HAS_REAL_CONTEXTVARS, | ||||||||||||||
| CONTEXTVARS_ERROR_MESSAGE, | ||||||||||||||
|
|
@@ -34,10 +36,14 @@ | |||||||||||||
|
|
||||||||||||||
| if TYPE_CHECKING: | ||||||||||||||
| from typing import Any | ||||||||||||||
| from typing import ContextManager | ||||||||||||||
| from typing import Optional | ||||||||||||||
| from typing import Dict | ||||||||||||||
| from typing import Callable | ||||||||||||||
| from typing import Generator | ||||||||||||||
| from typing import Union | ||||||||||||||
|
|
||||||||||||||
| from sentry_sdk.tracing import Span | ||||||||||||||
|
|
||||||||||||||
| from sentry_sdk._types import Event, EventProcessor | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -101,6 +107,9 @@ def sentry_log_exception( | |||||||||||||
| RequestHandler.log_exception = sentry_log_exception | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| _DEFAULT_TRANSACTION_NAME = "generic Tornado request" | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @contextlib.contextmanager | ||||||||||||||
| def _handle_request_impl(self: "RequestHandler") -> "Generator[None, None, None]": | ||||||||||||||
| integration = sentry_sdk.get_client().get_integration(TornadoIntegration) | ||||||||||||||
|
|
@@ -110,6 +119,8 @@ def _handle_request_impl(self: "RequestHandler") -> "Generator[None, None, None] | |||||||||||||
| return | ||||||||||||||
|
|
||||||||||||||
| weak_handler = weakref.ref(self) | ||||||||||||||
| client = sentry_sdk.get_client() | ||||||||||||||
| span_streaming = has_span_streaming_enabled(client.options) | ||||||||||||||
|
|
||||||||||||||
| with sentry_sdk.isolation_scope() as scope: | ||||||||||||||
| headers = self.request.headers | ||||||||||||||
|
|
@@ -118,22 +129,90 @@ def _handle_request_impl(self: "RequestHandler") -> "Generator[None, None, None] | |||||||||||||
| processor = _make_event_processor(weak_handler) | ||||||||||||||
| scope.add_event_processor(processor) | ||||||||||||||
|
|
||||||||||||||
| transaction = continue_trace( | ||||||||||||||
| headers, | ||||||||||||||
| op=OP.HTTP_SERVER, | ||||||||||||||
| # Like with all other integrations, this is our | ||||||||||||||
| # fallback transaction in case there is no route. | ||||||||||||||
| # sentry_urldispatcher_resolve is responsible for | ||||||||||||||
| # setting a transaction name later. | ||||||||||||||
| name="generic Tornado request", | ||||||||||||||
| source=TransactionSource.ROUTE, | ||||||||||||||
| origin=TornadoIntegration.origin, | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| with sentry_sdk.start_transaction( | ||||||||||||||
| transaction, custom_sampling_context={"tornado_request": self.request} | ||||||||||||||
| ): | ||||||||||||||
| yield | ||||||||||||||
| span_ctx: "ContextManager[Union[Span, StreamedSpan, None]]" | ||||||||||||||
|
|
||||||||||||||
| if span_streaming: | ||||||||||||||
| sentry_sdk.traces.continue_trace(dict(headers)) | ||||||||||||||
| scope.set_custom_sampling_context({"tornado_request": self.request}) | ||||||||||||||
|
|
||||||||||||||
| span_ctx = sentry_sdk.traces.start_span( | ||||||||||||||
| name=_DEFAULT_TRANSACTION_NAME, | ||||||||||||||
| attributes={ | ||||||||||||||
| "sentry.op": OP.HTTP_SERVER, | ||||||||||||||
| "sentry.origin": TornadoIntegration.origin, | ||||||||||||||
| "sentry.span.source": SegmentSource.ROUTE, | ||||||||||||||
| }, | ||||||||||||||
| ) | ||||||||||||||
| else: | ||||||||||||||
| transaction = continue_trace( | ||||||||||||||
| headers, | ||||||||||||||
| op=OP.HTTP_SERVER, | ||||||||||||||
| # Like with all other integrations, this is our | ||||||||||||||
| # fallback transaction in case there is no route. | ||||||||||||||
| # sentry_urldispatcher_resolve is responsible for | ||||||||||||||
| # setting a transaction name later. | ||||||||||||||
| name=_DEFAULT_TRANSACTION_NAME, | ||||||||||||||
| source=TransactionSource.ROUTE, | ||||||||||||||
| origin=TornadoIntegration.origin, | ||||||||||||||
| ) | ||||||||||||||
| span_ctx = sentry_sdk.start_transaction( | ||||||||||||||
| transaction, | ||||||||||||||
| custom_sampling_context={"tornado_request": self.request}, | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| with span_ctx as span: | ||||||||||||||
| if isinstance(span, StreamedSpan): | ||||||||||||||
| with capture_internal_exceptions(): | ||||||||||||||
| for attr, value in _get_request_attributes(self.request).items(): | ||||||||||||||
| span.set_attribute(attr, value) | ||||||||||||||
|
|
||||||||||||||
| method = getattr(self, self.request.method.lower(), None) | ||||||||||||||
| if method is not None: | ||||||||||||||
| tx_name = transaction_from_function(method) or "" | ||||||||||||||
| if tx_name: | ||||||||||||||
| span.name = tx_name | ||||||||||||||
|
Comment on lines
+171
to
+173
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fallback is dead code and I'm not sure if we want transaction concepts to bleed into the streaming path when it comes to variable naming.
Suggested change
|
||||||||||||||
| span.set_attribute( | ||||||||||||||
| "sentry.span.source", | ||||||||||||||
| SegmentSource.COMPONENT.value, | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 : both approaches work, but I noticed that this spot uses |
||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| try: | ||||||||||||||
| yield | ||||||||||||||
| finally: | ||||||||||||||
| if isinstance(span, StreamedSpan): | ||||||||||||||
| with capture_internal_exceptions(): | ||||||||||||||
| status_int = self.get_status() | ||||||||||||||
| span.set_attribute(SPANDATA.HTTP_STATUS_CODE, status_int) | ||||||||||||||
| span.status = "error" if status_int >= 400 else "ok" | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def _get_request_attributes(request: "Any") -> "Dict[str, Any]": | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we still capturing the request body in the streaming path? See https://getsentry.github.io/sentry-conventions/attributes/http/#http-request-body-data For prior art IIRC @ericapisani did this for another integration, not sure which exactly
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||
| attributes = {} # type: Dict[str, Any] | ||||||||||||||
|
|
||||||||||||||
| if request.method: | ||||||||||||||
| attributes[SPANDATA.HTTP_REQUEST_METHOD] = request.method.upper() | ||||||||||||||
|
|
||||||||||||||
| headers = _filter_headers(dict(request.headers), use_annotated_value=False) | ||||||||||||||
| for header, value in headers.items(): | ||||||||||||||
| attributes[f"http.request.header.{header.lower()}"] = value | ||||||||||||||
|
|
||||||||||||||
| if request.query: | ||||||||||||||
| attributes[SPANDATA.HTTP_QUERY] = request.query | ||||||||||||||
|
|
||||||||||||||
| attributes[SPANDATA.URL_FULL] = "%s://%s%s" % ( | ||||||||||||||
| request.protocol, | ||||||||||||||
| request.host, | ||||||||||||||
| request.path, | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| if request.protocol: | ||||||||||||||
| attributes["network.protocol.name"] = request.protocol | ||||||||||||||
|
|
||||||||||||||
| if should_send_default_pii() and request.remote_ip: | ||||||||||||||
| attributes["client.address"] = request.remote_ip | ||||||||||||||
| attributes["user.ip_address"] = request.remote_ip | ||||||||||||||
|
|
||||||||||||||
| return attributes | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @ensure_integration_enabled(TornadoIntegration) | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this is communicating a state rather than a "thing", would suggest a name like
is_span_streaming_enabled