Skip to content
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
64 changes: 54 additions & 10 deletions sentry_sdk/integrations/django/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,21 +451,19 @@ def _patch_django_asgi_handler() -> None:


def _set_transaction_name_and_source(
scope: "sentry_sdk.Scope", transaction_style: str, request: "WSGIRequest"
scope: "sentry_sdk.Scope",
transaction_style: str,
request: "WSGIRequest",
route_path: "Optional[str]",
) -> None:
try:
transaction_name = None
if transaction_style == "function_name":
fn = resolve(request.path).func
transaction_name = transaction_from_function(getattr(fn, "view_class", fn))

elif transaction_style == "url":
if hasattr(request, "urlconf"):
transaction_name = LEGACY_RESOLVER.resolve(
request.path_info, urlconf=request.urlconf
)
else:
transaction_name = LEGACY_RESOLVER.resolve(request.path_info)
elif transaction_style == "url" and route_path is not None:
transaction_name = route_path

if transaction_name is None:
transaction_name = request.path_info
Expand All @@ -477,6 +475,11 @@ def _set_transaction_name_and_source(
transaction_name,
source=source,
)

if transaction_style == "url" and route_path is not None:
server_span = scope._server_segment_span
if server_span is not None:
server_span.set_attribute(SPANDATA.HTTP_ROUTE, route_path)
except Resolver404:
urlconf = import_module(settings.ROOT_URLCONF)
# This exception only gets thrown when transaction_style is `function_name`
Expand All @@ -501,8 +504,29 @@ def _before_get_response(request: "WSGIRequest") -> None:
_patch_drf()

scope = sentry_sdk.get_current_scope()

route_path = None
if hasattr(request, "urlconf"):
try:
route_path = LEGACY_RESOLVER.resolve(
request.path_info, urlconf=request.urlconf
)
except Exception:
pass
else:
try:
route_path = LEGACY_RESOLVER.resolve(request.path_info)
except Exception:
pass

server_span = sentry_sdk.get_current_scope()._server_segment_span
if server_span is not None and route_path is not None:
server_span.set_attribute(SPANDATA.HTTP_ROUTE, route_path)

# Rely on WSGI middleware to start a trace
_set_transaction_name_and_source(scope, integration.transaction_style, request)
_set_transaction_name_and_source(
scope, integration.transaction_style, request, route_path=route_path
)

scope.add_event_processor(
_make_wsgi_request_event_processor(weakref.ref(request), integration)
Expand All @@ -520,7 +544,27 @@ def _attempt_resolve_again(
if not hasattr(request, "urlconf"):
return

_set_transaction_name_and_source(scope, transaction_style, request)
route_path = None
if hasattr(request, "urlconf"):
try:
route_path = LEGACY_RESOLVER.resolve(
request.path_info, urlconf=request.urlconf
)
except Exception:
pass
else:
try:
route_path = LEGACY_RESOLVER.resolve(request.path_info)
except Exception:
pass

server_span = sentry_sdk.get_current_scope()._server_segment_span
if server_span is not None and route_path is not None:
server_span.set_attribute(SPANDATA.HTTP_ROUTE, route_path)

_set_transaction_name_and_source(
scope, transaction_style, request, route_path=route_path
)
Comment thread
alexander-alderman-webb marked this conversation as resolved.


def _get_user_from_request_and_set_on_scope(request: "WSGIRequest") -> None:
Expand Down
27 changes: 27 additions & 0 deletions tests/integrations/django/asgi/test_asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,33 @@ async def test_async_views(
}


@pytest.mark.parametrize("application", APPS)
@pytest.mark.asyncio
@pytest.mark.skipif(
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
)
async def test_http_route(
sentry_init,
capture_items,
application,
):
sentry_init(
integrations=[DjangoIntegration()],
traces_sample_rate=1.0,
trace_lifecycle="stream",
)

items = capture_items("span")

comm = HttpCommunicator(application, "GET", "/async_message")
await comm.get_response()
await comm.wait()

sentry_sdk.flush()
(segment,) = (item.payload for item in items if item.payload.get("is_segment"))
assert segment["attributes"][SPANDATA.HTTP_ROUTE] == "/async_message"


@pytest.mark.parametrize("application", APPS)
@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
@pytest.mark.parametrize("middleware_spans", [False, True])
Expand Down
20 changes: 20 additions & 0 deletions tests/integrations/django/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,26 @@ def test_transaction_style_tracing_disabled(
assert event["transaction"] == expected_transaction


def test_http_route(
sentry_init,
client,
capture_items,
):
sentry_init(
integrations=[DjangoIntegration()],
traces_sample_rate=1.0,
trace_lifecycle="stream",
)

items = capture_items("span")

unpack_werkzeug_response(client.get("/message"))

sentry_sdk.flush()
(segment,) = (item.payload for item in items if item.payload.get("is_segment"))
assert segment["attributes"][SPANDATA.HTTP_ROUTE] == "/message"


@pytest.mark.parametrize("span_streaming", [True, False])
def test_request_body(
sentry_init,
Expand Down
Loading