Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 96 additions & 17 deletions sentry_sdk/integrations/tornado.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
span.set_attribute(
"sentry.span.source",
SegmentSource.COMPONENT.value,
)

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]":
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)
Expand Down
Loading
Loading