about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/sentry_sdk/integrations/tornado.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/sentry_sdk/integrations/tornado.py')
-rw-r--r--.venv/lib/python3.12/site-packages/sentry_sdk/integrations/tornado.py220
1 files changed, 220 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/sentry_sdk/integrations/tornado.py b/.venv/lib/python3.12/site-packages/sentry_sdk/integrations/tornado.py
new file mode 100644
index 00000000..3cd08752
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sentry_sdk/integrations/tornado.py
@@ -0,0 +1,220 @@
+import weakref
+import contextlib
+from inspect import iscoroutinefunction
+
+import sentry_sdk
+from sentry_sdk.api import continue_trace
+from sentry_sdk.consts import OP
+from sentry_sdk.scope import should_send_default_pii
+from sentry_sdk.tracing import TransactionSource
+from sentry_sdk.utils import (
+    HAS_REAL_CONTEXTVARS,
+    CONTEXTVARS_ERROR_MESSAGE,
+    ensure_integration_enabled,
+    event_from_exception,
+    capture_internal_exceptions,
+    transaction_from_function,
+)
+from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
+from sentry_sdk.integrations._wsgi_common import (
+    RequestExtractor,
+    _filter_headers,
+    _is_json_content_type,
+)
+from sentry_sdk.integrations.logging import ignore_logger
+
+try:
+    from tornado import version_info as TORNADO_VERSION
+    from tornado.web import RequestHandler, HTTPError
+    from tornado.gen import coroutine
+except ImportError:
+    raise DidNotEnable("Tornado not installed")
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from typing import Any
+    from typing import Optional
+    from typing import Dict
+    from typing import Callable
+    from typing import Generator
+
+    from sentry_sdk._types import Event, EventProcessor
+
+
+class TornadoIntegration(Integration):
+    identifier = "tornado"
+    origin = f"auto.http.{identifier}"
+
+    @staticmethod
+    def setup_once():
+        # type: () -> None
+        _check_minimum_version(TornadoIntegration, TORNADO_VERSION)
+
+        if not HAS_REAL_CONTEXTVARS:
+            # Tornado is async. We better have contextvars or we're going to leak
+            # state between requests.
+            raise DidNotEnable(
+                "The tornado integration for Sentry requires Python 3.7+ or the aiocontextvars package"
+                + CONTEXTVARS_ERROR_MESSAGE
+            )
+
+        ignore_logger("tornado.access")
+
+        old_execute = RequestHandler._execute
+
+        awaitable = iscoroutinefunction(old_execute)
+
+        if awaitable:
+            # Starting Tornado 6 RequestHandler._execute method is a standard Python coroutine (async/await)
+            # In that case our method should be a coroutine function too
+            async def sentry_execute_request_handler(self, *args, **kwargs):
+                # type: (RequestHandler, *Any, **Any) -> Any
+                with _handle_request_impl(self):
+                    return await old_execute(self, *args, **kwargs)
+
+        else:
+
+            @coroutine  # type: ignore
+            def sentry_execute_request_handler(self, *args, **kwargs):  # type: ignore
+                # type: (RequestHandler, *Any, **Any) -> Any
+                with _handle_request_impl(self):
+                    result = yield from old_execute(self, *args, **kwargs)
+                    return result
+
+        RequestHandler._execute = sentry_execute_request_handler
+
+        old_log_exception = RequestHandler.log_exception
+
+        def sentry_log_exception(self, ty, value, tb, *args, **kwargs):
+            # type: (Any, type, BaseException, Any, *Any, **Any) -> Optional[Any]
+            _capture_exception(ty, value, tb)
+            return old_log_exception(self, ty, value, tb, *args, **kwargs)
+
+        RequestHandler.log_exception = sentry_log_exception
+
+
+@contextlib.contextmanager
+def _handle_request_impl(self):
+    # type: (RequestHandler) -> Generator[None, None, None]
+    integration = sentry_sdk.get_client().get_integration(TornadoIntegration)
+
+    if integration is None:
+        yield
+
+    weak_handler = weakref.ref(self)
+
+    with sentry_sdk.isolation_scope() as scope:
+        headers = self.request.headers
+
+        scope.clear_breadcrumbs()
+        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
+
+
+@ensure_integration_enabled(TornadoIntegration)
+def _capture_exception(ty, value, tb):
+    # type: (type, BaseException, Any) -> None
+    if isinstance(value, HTTPError):
+        return
+
+    event, hint = event_from_exception(
+        (ty, value, tb),
+        client_options=sentry_sdk.get_client().options,
+        mechanism={"type": "tornado", "handled": False},
+    )
+
+    sentry_sdk.capture_event(event, hint=hint)
+
+
+def _make_event_processor(weak_handler):
+    # type: (Callable[[], RequestHandler]) -> EventProcessor
+    def tornado_processor(event, hint):
+        # type: (Event, dict[str, Any]) -> Event
+        handler = weak_handler()
+        if handler is None:
+            return event
+
+        request = handler.request
+
+        with capture_internal_exceptions():
+            method = getattr(handler, handler.request.method.lower())
+            event["transaction"] = transaction_from_function(method) or ""
+            event["transaction_info"] = {"source": TransactionSource.COMPONENT}
+
+        with capture_internal_exceptions():
+            extractor = TornadoRequestExtractor(request)
+            extractor.extract_into_event(event)
+
+            request_info = event["request"]
+
+            request_info["url"] = "%s://%s%s" % (
+                request.protocol,
+                request.host,
+                request.path,
+            )
+
+            request_info["query_string"] = request.query
+            request_info["method"] = request.method
+            request_info["env"] = {"REMOTE_ADDR": request.remote_ip}
+            request_info["headers"] = _filter_headers(dict(request.headers))
+
+        with capture_internal_exceptions():
+            if handler.current_user and should_send_default_pii():
+                event.setdefault("user", {}).setdefault("is_authenticated", True)
+
+        return event
+
+    return tornado_processor
+
+
+class TornadoRequestExtractor(RequestExtractor):
+    def content_length(self):
+        # type: () -> int
+        if self.request.body is None:
+            return 0
+        return len(self.request.body)
+
+    def cookies(self):
+        # type: () -> Dict[str, str]
+        return {k: v.value for k, v in self.request.cookies.items()}
+
+    def raw_data(self):
+        # type: () -> bytes
+        return self.request.body
+
+    def form(self):
+        # type: () -> Dict[str, Any]
+        return {
+            k: [v.decode("latin1", "replace") for v in vs]
+            for k, vs in self.request.body_arguments.items()
+        }
+
+    def is_json(self):
+        # type: () -> bool
+        return _is_json_content_type(self.request.headers.get("content-type"))
+
+    def files(self):
+        # type: () -> Dict[str, Any]
+        return {k: v[0] for k, v in self.request.files.items() if v}
+
+    def size_of_file(self, file):
+        # type: (Any) -> int
+        return len(file.body or ())