Skip to content

Commit 7a079f1

Browse files
Merge branch 'master' into jgreer013/issue-6596
2 parents 9056af4 + ad67968 commit 7a079f1

13 files changed

Lines changed: 222 additions & 51 deletions

File tree

sentry_sdk/integrations/_asgi_common.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ def _get_request_attributes(asgi_scope: "Any") -> "dict[str, Any]":
135135
if query_string is not None
136136
else url_without_query_string
137137
)
138+
attributes["url.path"] = asgi_scope.get("root_path", "") + asgi_scope.get(
139+
"path", ""
140+
)
138141

139142
client = asgi_scope.get("client")
140143
if client and should_send_default_pii():

sentry_sdk/integrations/aiohttp.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,17 @@ async def sentry_app_handle(
159159
header_value
160160
)
161161

162-
url_query_attribute = (
163-
{"url.query": request.query_string}
164-
if request.query_string
165-
else {}
166-
)
162+
url_attributes = {}
163+
if should_send_default_pii():
164+
url_attributes["url.full"] = "%s://%s%s" % (
165+
request.scheme,
166+
request.host,
167+
request.path,
168+
)
169+
url_attributes["url.path"] = request.path
170+
171+
if request.query_string:
172+
url_attributes["url.query"] = request.query_string
167173

168174
client_address_attributes = {}
169175
if should_send_default_pii() and request.remote:
@@ -180,10 +186,8 @@ async def sentry_app_handle(
180186
"sentry.op": OP.HTTP_SERVER,
181187
"sentry.origin": AioHttpIntegration.origin,
182188
"sentry.span.source": SegmentSource.ROUTE.value,
183-
"url.full": "%s://%s%s"
184-
% (request.scheme, request.host, request.path),
185189
"http.request.method": request.method,
186-
**url_query_attribute,
190+
**url_attributes,
187191
**client_address_attributes,
188192
**header_attributes,
189193
},
@@ -354,8 +358,10 @@ async def on_request_start(
354358
"sentry.origin": AioHttpIntegration.origin,
355359
"http.request.method": method,
356360
}
357-
if parsed_url is not None:
361+
if parsed_url is not None and should_send_default_pii():
358362
attributes["url.full"] = parsed_url.url
363+
attributes["url.path"] = params.url.path
364+
359365
if parsed_url.query:
360366
attributes["url.query"] = parsed_url.query
361367
if parsed_url.fragment:

sentry_sdk/integrations/redis/_async_common.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from typing import TYPE_CHECKING
22

33
import sentry_sdk
4-
from sentry_sdk.consts import OP
4+
from sentry_sdk.consts import OP, SPANDATA
55
from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
66
from sentry_sdk.integrations.redis.modules.caches import (
77
_compile_cache_span_properties,
88
_set_cache_data,
99
)
1010
from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
1111
from sentry_sdk.integrations.redis.utils import (
12+
_get_safe_command,
1213
_set_client_data,
1314
_set_pipeline_data,
1415
)
@@ -109,6 +110,12 @@ async def _sentry_execute_command(
109110
integration,
110111
)
111112

113+
additional_cache_span_attributes = {}
114+
with capture_internal_exceptions():
115+
additional_cache_span_attributes[SPANDATA.DB_QUERY_TEXT] = (
116+
_get_safe_command(name, args)
117+
)
118+
112119
cache_span: "Optional[Union[Span, StreamedSpan]]" = None
113120
if cache_properties["is_cache_key"] and cache_properties["op"] is not None:
114121
if span_streaming:
@@ -117,6 +124,7 @@ async def _sentry_execute_command(
117124
attributes={
118125
"sentry.op": cache_properties["op"],
119126
"sentry.origin": SPAN_ORIGIN,
127+
**additional_cache_span_attributes,
120128
},
121129
)
122130
else:
@@ -129,13 +137,20 @@ async def _sentry_execute_command(
129137

130138
db_properties = _compile_db_span_properties(integration, name, args)
131139

140+
additional_db_span_attributes = {}
141+
with capture_internal_exceptions():
142+
additional_db_span_attributes[SPANDATA.DB_QUERY_TEXT] = _get_safe_command(
143+
name, args
144+
)
145+
132146
db_span: "Union[Span, StreamedSpan]"
133147
if span_streaming:
134148
db_span = sentry_sdk.traces.start_span(
135149
name=db_properties["description"],
136150
attributes={
137151
"sentry.op": db_properties["op"],
138152
"sentry.origin": SPAN_ORIGIN,
153+
**additional_db_span_attributes,
139154
},
140155
)
141156
else:

sentry_sdk/integrations/redis/_sync_common.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from typing import TYPE_CHECKING
22

33
import sentry_sdk
4-
from sentry_sdk.consts import OP
4+
from sentry_sdk.consts import OP, SPANDATA
55
from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
66
from sentry_sdk.integrations.redis.modules.caches import (
77
_compile_cache_span_properties,
88
_set_cache_data,
99
)
1010
from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
1111
from sentry_sdk.integrations.redis.utils import (
12+
_get_safe_command,
1213
_set_client_data,
1314
_set_pipeline_data,
1415
)
@@ -108,6 +109,12 @@ def sentry_patched_execute_command(
108109
integration,
109110
)
110111

112+
additional_cache_span_attributes = {}
113+
with capture_internal_exceptions():
114+
additional_cache_span_attributes[SPANDATA.DB_QUERY_TEXT] = (
115+
_get_safe_command(name, args)
116+
)
117+
111118
cache_span: "Optional[Union[Span, StreamedSpan]]" = None
112119
if cache_properties["is_cache_key"] and cache_properties["op"] is not None:
113120
if span_streaming:
@@ -116,6 +123,7 @@ def sentry_patched_execute_command(
116123
attributes={
117124
"sentry.op": cache_properties["op"],
118125
"sentry.origin": SPAN_ORIGIN,
126+
**additional_cache_span_attributes,
119127
},
120128
)
121129
else:
@@ -128,13 +136,20 @@ def sentry_patched_execute_command(
128136

129137
db_properties = _compile_db_span_properties(integration, name, args)
130138

139+
additional_db_span_attributes = {}
140+
with capture_internal_exceptions():
141+
additional_db_span_attributes[SPANDATA.DB_QUERY_TEXT] = _get_safe_command(
142+
name, args
143+
)
144+
131145
db_span: "Union[Span, StreamedSpan]"
132146
if span_streaming:
133147
db_span = sentry_sdk.traces.start_span(
134148
name=db_properties["description"],
135149
attributes={
136150
"sentry.op": db_properties["op"],
137151
"sentry.origin": SPAN_ORIGIN,
152+
**additional_db_span_attributes,
138153
},
139154
)
140155
else:

sentry_sdk/integrations/wsgi.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -394,12 +394,6 @@ def _get_request_attributes(
394394
for header, value in headers.items():
395395
attributes[f"http.request.header.{header.lower()}"] = value
396396

397-
query_string = environ.get("QUERY_STRING")
398-
if query_string:
399-
attributes["http.query"] = query_string
400-
401-
attributes["url.full"] = get_request_url(environ, use_x_forwarded_for)
402-
403397
url_scheme = environ.get("wsgi.url_scheme")
404398
if url_scheme:
405399
attributes["network.protocol.name"] = url_scheme
@@ -420,4 +414,14 @@ def _get_request_attributes(
420414
if client_ip:
421415
attributes["client.address"] = client_ip
422416

417+
query_string = environ.get("QUERY_STRING")
418+
if query_string:
419+
attributes["http.query"] = query_string
420+
421+
path = environ.get("PATH_INFO", "")
422+
if path:
423+
attributes["url.path"] = path
424+
425+
attributes["url.full"] = get_request_url(environ, use_x_forwarded_for)
426+
423427
return attributes

tests/integrations/aiohttp/test_aiohttp.py

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,10 +1137,14 @@ async def handle(_):
11371137

11381138

11391139
@pytest.mark.asyncio
1140-
async def test_tracing_span_streaming(sentry_init, aiohttp_client, capture_items):
1140+
@pytest.mark.parametrize("send_pii", [True, False])
1141+
async def test_tracing_span_streaming(
1142+
sentry_init, aiohttp_client, capture_items, send_pii
1143+
):
11411144
sentry_init(
11421145
integrations=[AioHttpIntegration()],
11431146
traces_sample_rate=1.0,
1147+
send_default_pii=send_pii,
11441148
_experiments={"trace_lifecycle": "stream"},
11451149
)
11461150

@@ -1184,13 +1188,25 @@ async def hello(request):
11841188

11851189
# Request attributes derived directly from the aiohttp request.
11861190
assert server_span["attributes"]["http.request.method"] == "GET"
1187-
# client.address and user.ip_address is gated on send_default_pii (default False), so it must
1188-
# not be captured here.
1189-
assert "client.address" not in server_span["attributes"]
1190-
assert "user.ip_address" not in server_span["attributes"]
1191-
url_full = server_span["attributes"]["url.full"]
1192-
assert url_full.startswith("http://127.0.0.1:")
1193-
assert url_full.endswith("/")
1191+
1192+
if send_pii:
1193+
assert "client.address" in server_span["attributes"]
1194+
assert "user.ip_address" in server_span["attributes"]
1195+
1196+
url_full = server_span["attributes"]["url.full"]
1197+
assert url_full.startswith("http://127.0.0.1:")
1198+
assert url_full.endswith("/")
1199+
1200+
url_path = server_span["attributes"]["url.path"]
1201+
assert url_path == "/"
1202+
else:
1203+
assert "url.full" not in server_span["attributes"]
1204+
assert "url.path" not in server_span["attributes"]
1205+
assert "url.query" not in server_span["attributes"]
1206+
1207+
assert "client.address" not in server_span["attributes"]
1208+
assert "user.ip_address" not in server_span["attributes"]
1209+
11941210
# aiohttp's test client always sends a Host header; we assert it propagates
11951211
# into the span attributes via _filter_headers.
11961212
assert "http.request.header.host" in server_span["attributes"]
@@ -1280,12 +1296,14 @@ async def hello(request):
12801296

12811297

12821298
@pytest.mark.asyncio
1299+
@pytest.mark.parametrize("send_pii", [True, False])
12831300
async def test_url_query_attribute_span_streaming(
1284-
sentry_init, aiohttp_client, capture_items
1301+
sentry_init, aiohttp_client, capture_items, send_pii
12851302
):
12861303
sentry_init(
12871304
integrations=[AioHttpIntegration()],
12881305
traces_sample_rate=1.0,
1306+
send_default_pii=send_pii,
12891307
_experiments={"trace_lifecycle": "stream"},
12901308
)
12911309

@@ -1306,7 +1324,10 @@ async def hello(request):
13061324
assert len(items) == 2
13071325
server_segment, client_segment = [item.payload for item in items]
13081326

1309-
assert server_segment["attributes"]["url.query"] == "foo=bar&baz=qux"
1327+
if send_pii:
1328+
assert server_segment["attributes"]["url.query"] == "foo=bar&baz=qux"
1329+
else:
1330+
assert "url.query" not in server_segment["attributes"]
13101331

13111332

13121333
@pytest.mark.asyncio
@@ -1486,12 +1507,14 @@ async def hello(request):
14861507

14871508

14881509
@pytest.mark.asyncio
1510+
@pytest.mark.parametrize("send_pii", [True, False])
14891511
async def test_outgoing_client_span_span_streaming(
1490-
sentry_init, aiohttp_raw_server, aiohttp_client, capture_items
1512+
sentry_init, aiohttp_raw_server, aiohttp_client, capture_items, send_pii
14911513
):
14921514
sentry_init(
14931515
integrations=[AioHttpIntegration()],
14941516
traces_sample_rate=1.0,
1517+
send_default_pii=send_pii,
14951518
_experiments={"trace_lifecycle": "stream"},
14961519
)
14971520

@@ -1536,14 +1559,19 @@ async def hello(request):
15361559
assert inner_client_span["attributes"]["sentry.origin"] == "auto.http.aiohttp"
15371560
assert inner_client_span["attributes"]["http.request.method"] == "GET"
15381561
assert inner_client_span["attributes"]["http.response.status_code"] == 200
1539-
assert inner_client_span["attributes"]["url.query"] == "foo=bar"
15401562
assert inner_client_span["status"] == "ok"
15411563

1542-
url_full = inner_client_span["attributes"]["url.full"]
1543-
# parse_url() splits the URL — url.full is the base URL only, with the
1544-
# query string captured separately on url.query.
1545-
assert url_full.startswith("http://127.0.0.1:")
1546-
assert url_full.endswith("/")
1564+
if send_pii:
1565+
assert inner_client_span["attributes"]["url.query"] == "foo=bar"
1566+
1567+
url_full = inner_client_span["attributes"]["url.full"]
1568+
1569+
# parse_url() splits the URL — url.full is the base URL only, with the
1570+
# query string captured separately on url.query.
1571+
assert url_full.startswith("http://127.0.0.1:")
1572+
assert url_full.endswith("/")
1573+
1574+
assert inner_client_span["attributes"]["url.path"] == "/"
15471575

15481576

15491577
@pytest.mark.asyncio

tests/integrations/asgi/test_asgi.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ async def test_capture_transaction(
221221
span["attributes"]["url.full"]
222222
== "http://localhost/some_url?somevalue=123"
223223
)
224+
assert span["attributes"]["url.path"] == "/some_url"
224225
assert span["attributes"]["http.query"] == "somevalue=123"
225226

226227
else:
@@ -242,6 +243,30 @@ async def test_capture_transaction(
242243
}
243244

244245

246+
@pytest.mark.asyncio
247+
async def test_capture_transaction_with_root_path(
248+
sentry_init,
249+
asgi3_app,
250+
capture_items,
251+
):
252+
sentry_init(
253+
send_default_pii=True,
254+
traces_sample_rate=1.0,
255+
_experiments={"trace_lifecycle": "stream"},
256+
)
257+
app = SentryAsgiMiddleware(asgi3_app)
258+
259+
async with TestClient(app, scope={"root_path": "/api"}) as client:
260+
items = capture_items("span")
261+
await client.get("/some_url")
262+
263+
sentry_sdk.flush()
264+
265+
assert len(items) == 1
266+
span = items[0].payload
267+
assert span["attributes"]["url.path"] == "/api/some_url"
268+
269+
245270
@pytest.mark.asyncio
246271
@pytest.mark.parametrize(
247272
"span_streaming",

0 commit comments

Comments
 (0)