From 2df60fb8e2ac68b4b8e641e7ce8463bc298b25b9 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 15 Nov 2024 13:50:02 +0100 Subject: [PATCH 1/4] Extract span attrs from Tornado request --- sentry_sdk/integrations/tornado.py | 46 +++++++++++++++++++++- tests/integrations/tornado/test_tornado.py | 29 ++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) 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..2febf1956b 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,31 @@ 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", CrashingHandler)])) + client.fetch( + "/hi?foo=bar", headers={"Cookie": "name=value; name2=value2; name3=value3"} + ) From 68bb8775f420ac32749aef786ff67dea7e505d14 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 15 Nov 2024 13:52:59 +0100 Subject: [PATCH 2/4] migration guide --- MIGRATION_GUIDE.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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) | From a85caf8d4ce1d198c4c0ae91c969279a5c4bd96c Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 15 Nov 2024 13:55:39 +0100 Subject: [PATCH 3/4] dont need a crash --- tests/integrations/tornado/test_tornado.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/tornado/test_tornado.py b/tests/integrations/tornado/test_tornado.py index 2febf1956b..eb0a774706 100644 --- a/tests/integrations/tornado/test_tornado.py +++ b/tests/integrations/tornado/test_tornado.py @@ -475,7 +475,7 @@ def traces_sampler(sampling_context): traces_sampler=traces_sampler, ) - client = tornado_testcase(Application([(r"/hi", CrashingHandler)])) + client = tornado_testcase(Application([(r"/hi", HelloHandler)])) client.fetch( "/hi?foo=bar", headers={"Cookie": "name=value; name2=value2; name3=value3"} ) From 177d879a0c1b93b70429bfb39e1228b5435dc39f Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 15 Nov 2024 13:57:07 +0100 Subject: [PATCH 4/4] dont need cookies --- tests/integrations/tornado/test_tornado.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/integrations/tornado/test_tornado.py b/tests/integrations/tornado/test_tornado.py index eb0a774706..7ad974c535 100644 --- a/tests/integrations/tornado/test_tornado.py +++ b/tests/integrations/tornado/test_tornado.py @@ -476,6 +476,4 @@ def traces_sampler(sampling_context): ) client = tornado_testcase(Application([(r"/hi", HelloHandler)])) - client.fetch( - "/hi?foo=bar", headers={"Cookie": "name=value; name2=value2; name3=value3"} - ) + client.fetch("/hi?foo=bar")