Skip to content

Commit 95340a8

Browse files
authored
Merge branch 'potel-base' into potel-base-run-all-tests
2 parents 74bd4c8 + 82bf4f7 commit 95340a8

File tree

6 files changed

+133
-52
lines changed

6 files changed

+133
-52
lines changed

MIGRATION_GUIDE.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,18 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
2121
- clickhouse-driver integration: The query is now available under the `db.query.text` span attribute (only if `send_default_pii` is `True`).
2222
- `sentry_sdk.init` now returns `None` instead of a context manager.
2323
- The `sampling_context` argument of `traces_sampler` now additionally contains all span attributes known at span start.
24-
- The `sampling_context` argument of `traces_sampler` doesn't contain the `wsgi_environ` object anymore for WSGI frameworks. Instead, the individual properties of the environment are accessible, if available, as follows:
24+
- If you're using the AIOHTTP integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `aiohttp_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows:
25+
26+
| Request property | Sampling context key(s) |
27+
| ---------------- | ------------------------------- |
28+
| `path` | `url.path` |
29+
| `query_string` | `url.query` |
30+
| `method` | `http.request.method` |
31+
| `host` | `server.address`, `server.port` |
32+
| `scheme` | `url.scheme` |
33+
| full URL | `url.full` |
34+
35+
- 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:
2536

2637
| Env property | Sampling context key(s) |
2738
| ----------------- | ------------------------------------------------- |
@@ -34,7 +45,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
3445
| `wsgi.url_scheme` | `url.scheme` |
3546
| full URL | `url.full` |
3647

37-
- The `sampling_context` argument of `traces_sampler` doesn't contain the `asgi_scope` object anymore for ASGI frameworks. Instead, the individual properties of the scope, if available, are accessible as follows:
48+
- If you're using the generic ASGI integration, the `sampling_context` argument of `traces_sampler` doesn't contain the `asgi_scope` object anymore. Instead, the individual properties of the scope, if available, are accessible as follows:
3849

3950
| Scope property | Sampling context key(s) |
4051
| -------------- | ------------------------------- |

sentry_sdk/integrations/aiohttp.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@
6565

6666
TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern")
6767

68+
REQUEST_PROPERTY_TO_ATTRIBUTE = {
69+
"query_string": "url.query",
70+
"method": "http.request.method",
71+
"scheme": "url.scheme",
72+
"path": "url.path",
73+
}
74+
6875

6976
class AioHttpIntegration(Integration):
7077
identifier = "aiohttp"
@@ -127,19 +134,19 @@ async def sentry_app_handle(self, request, *args, **kwargs):
127134

128135
headers = dict(request.headers)
129136
with sentry_sdk.continue_trace(headers):
130-
with sentry_sdk.start_transaction(
137+
with sentry_sdk.start_span(
131138
op=OP.HTTP_SERVER,
132139
# If this transaction name makes it to the UI, AIOHTTP's
133140
# URL resolver did not find a route or died trying.
134141
name="generic AIOHTTP request",
135142
source=TRANSACTION_SOURCE_ROUTE,
136143
origin=AioHttpIntegration.origin,
137-
custom_sampling_context={"aiohttp_request": request},
138-
) as transaction:
144+
attributes=_prepopulate_attributes(request),
145+
) as span:
139146
try:
140147
response = await old_handle(self, request)
141148
except HTTPException as e:
142-
transaction.set_http_status(e.status_code)
149+
span.set_http_status(e.status_code)
143150

144151
if (
145152
e.status_code
@@ -149,14 +156,14 @@ async def sentry_app_handle(self, request, *args, **kwargs):
149156

150157
raise
151158
except (asyncio.CancelledError, ConnectionResetError):
152-
transaction.set_status(SPANSTATUS.CANCELLED)
159+
span.set_status(SPANSTATUS.CANCELLED)
153160
raise
154161
except Exception:
155162
# This will probably map to a 500 but seems like we
156163
# have no way to tell. Do not set span status.
157164
reraise(*_capture_exception())
158165

159-
transaction.set_http_status(response.status)
166+
span.set_http_status(response.status)
160167
return response
161168

162169
Application._handle = sentry_app_handle
@@ -363,3 +370,30 @@ def get_aiohttp_request_data(request):
363370

364371
# request has no body
365372
return None
373+
374+
375+
def _prepopulate_attributes(request):
376+
# type: (Request) -> dict[str, Any]
377+
"""Construct initial span attributes that can be used in traces sampler."""
378+
attributes = {}
379+
380+
for prop, attr in REQUEST_PROPERTY_TO_ATTRIBUTE.items():
381+
if getattr(request, prop, None) is not None:
382+
attributes[attr] = getattr(request, prop)
383+
384+
if getattr(request, "host", None) is not None:
385+
try:
386+
host, port = request.host.split(":")
387+
attributes["server.address"] = host
388+
attributes["server.port"] = port
389+
except ValueError:
390+
attributes["server.address"] = request.host
391+
392+
try:
393+
url = f"{request.scheme}://{request.host}{request.path}"
394+
if request.query_string:
395+
attributes["url.full"] = f"{url}?{request.query_string}"
396+
except Exception:
397+
pass
398+
399+
return attributes

sentry_sdk/integrations/opentelemetry/sampler.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ def should_sample(
153153
}
154154
sampling_context.update(attributes)
155155
sample_rate = client.options["traces_sampler"](sampling_context)
156-
157156
else:
158157
# Check if there is a parent with a sampling decision
159158
parent_sampled = get_parent_sampled(parent_span_context, trace_id)

sentry_sdk/integrations/rust_tracing.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,25 @@ def __init__(
151151
[Dict[str, Any]], EventTypeMapping
152152
] = default_event_type_mapping,
153153
span_filter: Callable[[Dict[str, Any]], bool] = default_span_filter,
154-
send_sensitive_data: Optional[bool] = None,
154+
include_tracing_fields: Optional[bool] = None,
155155
):
156156
self.origin = origin
157157
self.event_type_mapping = event_type_mapping
158158
self.span_filter = span_filter
159-
self.send_sensitive_data = send_sensitive_data
159+
self.include_tracing_fields = include_tracing_fields
160+
161+
def _include_tracing_fields(self) -> bool:
162+
"""
163+
By default, the values of tracing fields are not included in case they
164+
contain PII. A user may override that by passing `True` for the
165+
`include_tracing_fields` keyword argument of this integration or by
166+
setting `send_default_pii` to `True` in their Sentry client options.
167+
"""
168+
return (
169+
should_send_default_pii()
170+
if self.include_tracing_fields is None
171+
else self.include_tracing_fields
172+
)
160173

161174
def on_event(self, event: str, _span_state: TraceState) -> None:
162175
deserialized_event = json.loads(event)
@@ -207,7 +220,10 @@ def on_new_span(self, attrs: str, span_id: str) -> TraceState:
207220

208221
fields = metadata.get("fields", [])
209222
for field in fields:
210-
sentry_span.set_data(field, attrs.get(field))
223+
if self._include_tracing_fields():
224+
sentry_span.set_data(field, attrs.get(field))
225+
else:
226+
sentry_span.set_data(field, SENSITIVE_DATA_SUBSTITUTE)
211227

212228
scope.span = sentry_span
213229
return (parent_sentry_span, sentry_span)
@@ -225,15 +241,9 @@ def on_record(self, span_id: str, values: str, span_state: TraceState) -> None:
225241
return
226242
_parent_sentry_span, sentry_span = span_state
227243

228-
send_sensitive_data = (
229-
should_send_default_pii()
230-
if self.send_sensitive_data is None
231-
else self.send_sensitive_data
232-
)
233-
234244
deserialized_values = json.loads(values)
235245
for key, value in deserialized_values.items():
236-
if send_sensitive_data:
246+
if self._include_tracing_fields():
237247
sentry_span.set_data(key, value)
238248
else:
239249
sentry_span.set_data(key, SENSITIVE_DATA_SUBSTITUTE)
@@ -259,12 +269,12 @@ def __init__(
259269
[Dict[str, Any]], EventTypeMapping
260270
] = default_event_type_mapping,
261271
span_filter: Callable[[Dict[str, Any]], bool] = default_span_filter,
262-
send_sensitive_data: Optional[bool] = None,
272+
include_tracing_fields: Optional[bool] = None,
263273
):
264274
self.identifier = identifier
265275
origin = f"auto.function.rust_tracing.{identifier}"
266276
self.tracing_layer = RustTracingLayer(
267-
origin, event_type_mapping, span_filter, send_sensitive_data
277+
origin, event_type_mapping, span_filter, include_tracing_fields
268278
)
269279

270280
initializer(self.tracing_layer)

tests/integrations/aiohttp/test_aiohttp.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import asyncio
22
import json
3+
import re
34
from contextlib import suppress
45
from unittest import mock
56

67
import pytest
78
from aiohttp import web, ClientSession
89
from aiohttp.client import ServerDisconnectedError
9-
from aiohttp.web_request import Request
1010
from aiohttp.web_exceptions import (
1111
HTTPInternalServerError,
1212
HTTPNetworkAuthenticationRequired,
@@ -291,13 +291,12 @@ async def hello(request):
291291

292292

293293
@pytest.mark.asyncio
294-
async def test_traces_sampler_gets_request_object_in_sampling_context(
294+
async def test_traces_sampler_gets_attributes_in_sampling_context(
295295
sentry_init,
296296
aiohttp_client,
297-
DictionaryContaining, # noqa: N803
298-
ObjectDescribedBy, # noqa: N803
299297
):
300-
traces_sampler = mock.Mock()
298+
traces_sampler = mock.Mock(return_value=True)
299+
301300
sentry_init(
302301
integrations=[AioHttpIntegration()],
303302
traces_sampler=traces_sampler,
@@ -310,17 +309,21 @@ async def kangaroo_handler(request):
310309
app.router.add_get("/tricks/kangaroo", kangaroo_handler)
311310

312311
client = await aiohttp_client(app)
313-
await client.get("/tricks/kangaroo")
314-
315-
traces_sampler.assert_any_call(
316-
DictionaryContaining(
317-
{
318-
"aiohttp_request": ObjectDescribedBy(
319-
type=Request, attrs={"method": "GET", "path": "/tricks/kangaroo"}
320-
)
321-
}
322-
)
312+
await client.get("/tricks/kangaroo?jump=high")
313+
314+
assert traces_sampler.call_count == 1
315+
sampling_context = traces_sampler.call_args_list[0][0][0]
316+
assert isinstance(sampling_context, dict)
317+
assert re.match(
318+
r"http:\/\/127\.0\.0\.1:[0-9]{4,5}\/tricks\/kangaroo\?jump=high",
319+
sampling_context["url.full"],
323320
)
321+
assert sampling_context["url.path"] == "/tricks/kangaroo"
322+
assert sampling_context["url.query"] == "jump=high"
323+
assert sampling_context["url.scheme"] == "http"
324+
assert sampling_context["http.request.method"] == "GET"
325+
assert sampling_context["server.address"] == "127.0.0.1"
326+
assert sampling_context["server.port"].isnumeric()
324327

325328

326329
@pytest.mark.asyncio
@@ -574,17 +577,16 @@ async def handler(request):
574577
client = await aiohttp_client(raw_server)
575578
resp = await client.get("/", headers={"bagGage": "custom=value"})
576579

577-
assert (
578-
sorted(resp.request_info.headers["baggage"].split(","))
579-
== sorted([
580+
assert sorted(resp.request_info.headers["baggage"].split(",")) == sorted(
581+
[
580582
"custom=value",
581583
f"sentry-trace_id={transaction.trace_id}",
582584
"sentry-environment=production",
583585
"sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42",
584586
"sentry-transaction=/interactions/other-dogs/new-dog",
585587
"sentry-sample_rate=1.0",
586588
"sentry-sampled=true",
587-
])
589+
]
588590
)
589591

590592

tests/integrations/rust_tracing/test_rust_tracing.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from unittest import mock
12
import pytest
23

34
from string import Template
@@ -66,7 +67,9 @@ def record(self, span_id: int):
6667
def test_on_new_span_on_close(sentry_init, capture_events):
6768
rust_tracing = FakeRustTracing()
6869
integration = RustTracingIntegration(
69-
"test_on_new_span_on_close", rust_tracing.set_layer_impl
70+
"test_on_new_span_on_close",
71+
initializer=rust_tracing.set_layer_impl,
72+
include_tracing_fields=True,
7073
)
7174
sentry_init(integrations=[integration], traces_sample_rate=1.0)
7275

@@ -105,7 +108,9 @@ def test_on_new_span_on_close(sentry_init, capture_events):
105108
def test_nested_on_new_span_on_close(sentry_init, capture_events):
106109
rust_tracing = FakeRustTracing()
107110
integration = RustTracingIntegration(
108-
"test_nested_on_new_span_on_close", rust_tracing.set_layer_impl
111+
"test_nested_on_new_span_on_close",
112+
initializer=rust_tracing.set_layer_impl,
113+
include_tracing_fields=True,
109114
)
110115
sentry_init(integrations=[integration], traces_sample_rate=1.0)
111116

@@ -331,7 +336,10 @@ def span_filter(metadata: Dict[str, object]) -> bool:
331336

332337
rust_tracing = FakeRustTracing()
333338
integration = RustTracingIntegration(
334-
"test_span_filter", rust_tracing.set_layer_impl, span_filter=span_filter
339+
"test_span_filter",
340+
initializer=rust_tracing.set_layer_impl,
341+
span_filter=span_filter,
342+
include_tracing_fields=True,
335343
)
336344
sentry_init(integrations=[integration], traces_sample_rate=1.0)
337345

@@ -365,7 +373,7 @@ def test_record(sentry_init):
365373
integration = RustTracingIntegration(
366374
"test_record",
367375
initializer=rust_tracing.set_layer_impl,
368-
send_sensitive_data=True,
376+
include_tracing_fields=True,
369377
)
370378
sentry_init(integrations=[integration], traces_sample_rate=1.0)
371379

@@ -391,6 +399,7 @@ def span_filter(metadata: Dict[str, object]) -> bool:
391399
"test_record_in_ignored_span",
392400
rust_tracing.set_layer_impl,
393401
span_filter=span_filter,
402+
include_tracing_fields=True,
394403
)
395404
sentry_init(integrations=[integration], traces_sample_rate=1.0)
396405

@@ -409,7 +418,7 @@ def span_filter(metadata: Dict[str, object]) -> bool:
409418

410419

411420
@pytest.mark.parametrize(
412-
"send_default_pii, send_sensitive_data, sensitive_data_expected",
421+
"send_default_pii, include_tracing_fields, tracing_fields_expected",
413422
[
414423
(True, True, True),
415424
(True, False, False),
@@ -419,14 +428,14 @@ def span_filter(metadata: Dict[str, object]) -> bool:
419428
(False, None, False),
420429
],
421430
)
422-
def test_sensitive_data(
423-
sentry_init, send_default_pii, send_sensitive_data, sensitive_data_expected
431+
def test_include_tracing_fields(
432+
sentry_init, send_default_pii, include_tracing_fields, tracing_fields_expected
424433
):
425434
rust_tracing = FakeRustTracing()
426435
integration = RustTracingIntegration(
427436
"test_record",
428437
initializer=rust_tracing.set_layer_impl,
429-
send_sensitive_data=send_sensitive_data,
438+
include_tracing_fields=include_tracing_fields,
430439
)
431440

432441
sentry_init(
@@ -438,13 +447,29 @@ def test_sensitive_data(
438447
rust_tracing.new_span(RustTracingLevel.Info, 3)
439448

440449
span_before_record = sentry_sdk.get_current_span().to_json()
441-
assert span_before_record["data"]["version"] is None
450+
if tracing_fields_expected:
451+
assert span_before_record["data"]["version"] is None
452+
else:
453+
assert span_before_record["data"]["version"] == "[Filtered]"
442454

443455
rust_tracing.record(3)
444456

445457
span_after_record = sentry_sdk.get_current_span().to_json()
446458

447-
if sensitive_data_expected:
448-
assert span_after_record["data"]["version"] == "memoized"
459+
if tracing_fields_expected:
460+
assert span_after_record["data"] == {
461+
"thread.id": mock.ANY,
462+
"thread.name": mock.ANY,
463+
"use_memoized": True,
464+
"version": "memoized",
465+
"index": 10,
466+
}
467+
449468
else:
450-
assert span_after_record["data"]["version"] == "[Filtered]"
469+
assert span_after_record["data"] == {
470+
"thread.id": mock.ANY,
471+
"thread.name": mock.ANY,
472+
"use_memoized": "[Filtered]",
473+
"version": "[Filtered]",
474+
"index": "[Filtered]",
475+
}

0 commit comments

Comments
 (0)