Skip to content

Commit

Permalink
Extract span attrs from Tornado request (#3784)
Browse files Browse the repository at this point in the history
  • Loading branch information
sentrivana authored Nov 15, 2024
1 parent 82bf4f7 commit 1c147e9
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 2 deletions.
12 changes: 12 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
46 changes: 44 additions & 2 deletions sentry_sdk/integrations/tornado.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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}"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
27 changes: 27 additions & 0 deletions tests/integrations/tornado/test_tornado.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import re

import pytest

Expand Down Expand Up @@ -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")

0 comments on commit 1c147e9

Please sign in to comment.