diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index fce361a9ec..5d0777c22a 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -32,6 +32,18 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh | `scheme` | `url.scheme` | | full URL | `url.full` | +- If you're using the Tornado integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `tornado_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: + + | Request property | Sampling context key(s) | + | ---------------- | --------------------------------------------------- | + | `path` | `url.path` | + | `query` | `url.query` | + | `protocol` | `url.scheme` | + | `method` | `http.request.method` | + | `host` | `server.address`, `server.port` | + | `version` | `network.protocol.name`, `network.protocol.version` | + | full URL | `url.full` | + - If you're using the generic WSGI integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `wsgi_environ` object anymore. Instead, the individual properties of the environment are accessible, if available, as follows: | Env property | Sampling context key(s) | diff --git a/sentry_sdk/integrations/tornado.py b/sentry_sdk/integrations/tornado.py index 21532fbba5..591f59ec03 100644 --- a/sentry_sdk/integrations/tornado.py +++ b/sentry_sdk/integrations/tornado.py @@ -27,8 +27,9 @@ try: from tornado import version_info as TORNADO_VERSION - from tornado.web import RequestHandler, HTTPError from tornado.gen import coroutine + from tornado.httputil import HTTPServerRequest + from tornado.web import RequestHandler, HTTPError except ImportError: raise DidNotEnable("Tornado not installed") @@ -44,6 +45,14 @@ from sentry_sdk._types import Event, EventProcessor +REQUEST_PROPERTY_TO_ATTRIBUTE = { + "method": "http.request.method", + "path": "url.path", + "query": "url.query", + "protocol": "url.scheme", +} + + class TornadoIntegration(Integration): identifier = "tornado" origin = f"auto.http.{identifier}" @@ -124,7 +133,7 @@ def _handle_request_impl(self): name="generic Tornado request", source=TRANSACTION_SOURCE_ROUTE, origin=TornadoIntegration.origin, - custom_sampling_context={"tornado_request": self.request}, + attributes=_prepopulate_attributes(self.request), ): yield @@ -218,3 +227,36 @@ def files(self): def size_of_file(self, file): # type: (Any) -> int return len(file.body or ()) + + +def _prepopulate_attributes(request): + # type: (HTTPServerRequest) -> dict[str, Any] + # https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPServerRequest + attributes = {} + + for prop, attr in REQUEST_PROPERTY_TO_ATTRIBUTE.items(): + if getattr(request, prop, None) is not None: + attributes[attr] = getattr(request, prop) + + if getattr(request, "version", None): + try: + proto, version = request.version.split("/") + attributes["network.protocol.name"] = proto + attributes["network.protocol.version"] = version + except ValueError: + attributes["network.protocol.name"] = request.version + + if getattr(request, "host", None) is not None: + try: + address, port = request.host.split(":") + attributes["server.address"] = address + attributes["server.port"] = port + except ValueError: + attributes["server.address"] = request.host + + try: + attributes["url.full"] = request.full_url() + except Exception: + pass + + return attributes diff --git a/tests/integrations/tornado/test_tornado.py b/tests/integrations/tornado/test_tornado.py index 294f605f6a..7ad974c535 100644 --- a/tests/integrations/tornado/test_tornado.py +++ b/tests/integrations/tornado/test_tornado.py @@ -1,4 +1,5 @@ import json +import re import pytest @@ -450,3 +451,29 @@ def test_span_origin(tornado_testcase, sentry_init, capture_events): (_, event) = events assert event["contexts"]["trace"]["origin"] == "auto.http.tornado" + + +def test_attributes_in_traces_sampler(tornado_testcase, sentry_init): + def traces_sampler(sampling_context): + assert sampling_context["url.query"] == "foo=bar" + assert sampling_context["url.path"] == "/hi" + assert sampling_context["url.scheme"] == "http" + assert re.match( + r"http:\/\/127\.0\.0\.1:[0-9]{4,5}\/hi\?foo=bar", + sampling_context["url.full"], + ) + assert sampling_context["http.request.method"] == "GET" + assert sampling_context["server.address"] == "127.0.0.1" + assert sampling_context["server.port"].isnumeric() + assert sampling_context["network.protocol.name"] == "HTTP" + assert sampling_context["network.protocol.version"] == "1.1" + + return True + + sentry_init( + integrations=[TornadoIntegration], + traces_sampler=traces_sampler, + ) + + client = tornado_testcase(Application([(r"/hi", HelloHandler)])) + client.fetch("/hi?foo=bar")