Skip to content

Commit 1c147e9

Browse files
authored
Extract span attrs from Tornado request (#3784)
1 parent 82bf4f7 commit 1c147e9

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

MIGRATION_GUIDE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
3232
| `scheme` | `url.scheme` |
3333
| full URL | `url.full` |
3434

35+
- 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:
36+
37+
| Request property | Sampling context key(s) |
38+
| ---------------- | --------------------------------------------------- |
39+
| `path` | `url.path` |
40+
| `query` | `url.query` |
41+
| `protocol` | `url.scheme` |
42+
| `method` | `http.request.method` |
43+
| `host` | `server.address`, `server.port` |
44+
| `version` | `network.protocol.name`, `network.protocol.version` |
45+
| full URL | `url.full` |
46+
3547
- 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:
3648

3749
| Env property | Sampling context key(s) |

sentry_sdk/integrations/tornado.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727

2828
try:
2929
from tornado import version_info as TORNADO_VERSION
30-
from tornado.web import RequestHandler, HTTPError
3130
from tornado.gen import coroutine
31+
from tornado.httputil import HTTPServerRequest
32+
from tornado.web import RequestHandler, HTTPError
3233
except ImportError:
3334
raise DidNotEnable("Tornado not installed")
3435

@@ -44,6 +45,14 @@
4445
from sentry_sdk._types import Event, EventProcessor
4546

4647

48+
REQUEST_PROPERTY_TO_ATTRIBUTE = {
49+
"method": "http.request.method",
50+
"path": "url.path",
51+
"query": "url.query",
52+
"protocol": "url.scheme",
53+
}
54+
55+
4756
class TornadoIntegration(Integration):
4857
identifier = "tornado"
4958
origin = f"auto.http.{identifier}"
@@ -124,7 +133,7 @@ def _handle_request_impl(self):
124133
name="generic Tornado request",
125134
source=TRANSACTION_SOURCE_ROUTE,
126135
origin=TornadoIntegration.origin,
127-
custom_sampling_context={"tornado_request": self.request},
136+
attributes=_prepopulate_attributes(self.request),
128137
):
129138
yield
130139

@@ -218,3 +227,36 @@ def files(self):
218227
def size_of_file(self, file):
219228
# type: (Any) -> int
220229
return len(file.body or ())
230+
231+
232+
def _prepopulate_attributes(request):
233+
# type: (HTTPServerRequest) -> dict[str, Any]
234+
# https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPServerRequest
235+
attributes = {}
236+
237+
for prop, attr in REQUEST_PROPERTY_TO_ATTRIBUTE.items():
238+
if getattr(request, prop, None) is not None:
239+
attributes[attr] = getattr(request, prop)
240+
241+
if getattr(request, "version", None):
242+
try:
243+
proto, version = request.version.split("/")
244+
attributes["network.protocol.name"] = proto
245+
attributes["network.protocol.version"] = version
246+
except ValueError:
247+
attributes["network.protocol.name"] = request.version
248+
249+
if getattr(request, "host", None) is not None:
250+
try:
251+
address, port = request.host.split(":")
252+
attributes["server.address"] = address
253+
attributes["server.port"] = port
254+
except ValueError:
255+
attributes["server.address"] = request.host
256+
257+
try:
258+
attributes["url.full"] = request.full_url()
259+
except Exception:
260+
pass
261+
262+
return attributes

tests/integrations/tornado/test_tornado.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import re
23

34
import pytest
45

@@ -450,3 +451,29 @@ def test_span_origin(tornado_testcase, sentry_init, capture_events):
450451
(_, event) = events
451452

452453
assert event["contexts"]["trace"]["origin"] == "auto.http.tornado"
454+
455+
456+
def test_attributes_in_traces_sampler(tornado_testcase, sentry_init):
457+
def traces_sampler(sampling_context):
458+
assert sampling_context["url.query"] == "foo=bar"
459+
assert sampling_context["url.path"] == "/hi"
460+
assert sampling_context["url.scheme"] == "http"
461+
assert re.match(
462+
r"http:\/\/127\.0\.0\.1:[0-9]{4,5}\/hi\?foo=bar",
463+
sampling_context["url.full"],
464+
)
465+
assert sampling_context["http.request.method"] == "GET"
466+
assert sampling_context["server.address"] == "127.0.0.1"
467+
assert sampling_context["server.port"].isnumeric()
468+
assert sampling_context["network.protocol.name"] == "HTTP"
469+
assert sampling_context["network.protocol.version"] == "1.1"
470+
471+
return True
472+
473+
sentry_init(
474+
integrations=[TornadoIntegration],
475+
traces_sampler=traces_sampler,
476+
)
477+
478+
client = tornado_testcase(Application([(r"/hi", HelloHandler)]))
479+
client.fetch("/hi?foo=bar")

0 commit comments

Comments
 (0)