Skip to content

Extract span attrs from Tornado request #3784

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
Loading