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
36 changes: 18 additions & 18 deletions sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,7 @@ def __init__(
transport_queue_size: int = DEFAULT_QUEUE_SIZE,
sample_rate: float = 1.0,
send_default_pii: "Optional[bool]" = None,
data_collection: "Optional[DataCollectionUserOptions]" = None,
http_proxy: "Optional[str]" = None,
https_proxy: "Optional[str]" = None,
ignore_errors: "Sequence[Union[type, str]]" = [], # noqa: B006
Expand Down Expand Up @@ -1472,6 +1473,23 @@ def __init__(
If you enable this option, be sure to manually remove what you don't want to send using our features for
managing `Sensitive Data <https://docs.sentry.io/data-management/sensitive-data/>`_.

:param data_collection: Structured configuration controlling what data integrations collect
automatically, superseding `send_default_pii`. Passing a dict opts into the feature; omitted
fields use their defaults (most categories are collected, with the sensitive denylist
scrubbing values). When it is not set, the SDK derives behaviour from `send_default_pii` so
that upgrading changes nothing. Restrict collection per category (user identity, cookies,
HTTP headers/bodies, query params, generative AI inputs/outputs, stack frame variables,
source context). If `send_default_pii` is also set, `data_collection` takes precedence.

Example::

sentry_sdk.init(
dsn="...",
data_collection={"user_info": False, "http_bodies": []},
)

See https://docs.sentry.io/platforms/python/configuration/options/#data_collection for more details.

:param event_scrubber: Scrubs the event payload for sensitive information such as cookies, sessions, and
passwords from a `denylist`.

Expand Down Expand Up @@ -1807,24 +1825,6 @@ def __init__(
`trace_lifecycle="stream"` is enabled.

:param _experiments: Dictionary of experimental, opt-in features that are not yet stable.

``data_collection`` (EXPERIMENTAL): structured configuration controlling what data integrations
collect automatically, superseding `send_default_pii`. Passing a dict under
`_experiments={"data_collection": {...}}` opts into the feature; omitted fields use their
defaults (most categories are collected, with the sensitive denylist scrubbing values).
When it is not set, the SDK derives behaviour from `send_default_pii` so that upgrading
changes nothing. Restrict collection per category (user identity, cookies, HTTP
headers/bodies, query params, generative AI inputs/outputs, stack frame variables, source
context). If `send_default_pii` is also set, `data_collection` takes precedence.

Example::

sentry_sdk.init(
dsn="...",
_experiments={"data_collection": {"user_info": False, "http_bodies": []}},
)

See https://docs.sentry.io/platforms/python/configuration/options/#data_collection for more details.
"""
pass

Expand Down
9 changes: 8 additions & 1 deletion sentry_sdk/data_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,15 @@ def _resolve_data_collection(options: "Dict[str, Any]") -> "DataCollection":
concrete values for every field.

``data_collection`` must be a plain ``dict``.

Must be called exactly once per options dict, before ``client._get_options``
overwrites ``options["data_collection"]`` with the resolved result. Feeding an
already-resolved dict back in would flip ``provided_by_user`` to ``True``.
"""
user_dc = options.get("_experiments", {}).get("data_collection")
Comment thread
sentrivana marked this conversation as resolved.
user_dc = options.get("data_collection")
if user_dc is None:
user_dc = options.get("_experiments", {}).get("data_collection")

send_default_pii = options.get("send_default_pii")

include_local_variables = (
Expand Down
9 changes: 8 additions & 1 deletion sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@

from sentry_sdk._types import (
AttributeValue,
DataCollection,
Event,
ExcInfo,
Hint,
Expand Down Expand Up @@ -2109,7 +2110,13 @@ def has_data_collection_enabled(options: "Optional[dict[str, Any]]") -> bool:
if options is None:
return False

return "data_collection" in options.get("_experiments", {})
data_collection: "Optional[DataCollection]" = options.get("data_collection")
# Client options are resolved as part of client initialization, so `data_collection`
# being None could be that the user just didn't provide it.
# `provided_by_user` is what actually records whether the user actually configured it.
return data_collection is not None and data_collection.get(
"provided_by_user", False
)


def get_before_send_log(
Expand Down
62 changes: 23 additions & 39 deletions tests/integrations/aiohttp/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ async def test_aiohttp_request_body_data_collection(
):
sentry_init(
integrations=[AioHttpIntegration()],
_experiments={"data_collection": data_collection},
data_collection=data_collection,
)

body = {"some": "value"}
Expand Down Expand Up @@ -207,7 +207,7 @@ async def test_aiohttp_oversized_request_body_data_collection(
sentry_init(
integrations=[AioHttpIntegration()],
max_request_body_size="small",
_experiments={"data_collection": data_collection},
data_collection=data_collection,
)

body = "a" * 2000
Expand Down Expand Up @@ -643,21 +643,17 @@ async def handler(request):
({"send_default_pii": False}, False, False),
(
{
"_experiments": {
"data_collection": {
"url_query_params": {"mode": "denylist", "terms": []}
}
"data_collection": {
"url_query_params": {"mode": "denylist", "terms": []}
}
},
True,
True,
),
(
{
"_experiments": {
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": []}
}
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": []}
}
},
True,
Expand Down Expand Up @@ -794,21 +790,17 @@ async def handler(request):
({"send_default_pii": False}, False, False),
(
{
"_experiments": {
"data_collection": {
"url_query_params": {"mode": "denylist", "terms": []}
}
"data_collection": {
"url_query_params": {"mode": "denylist", "terms": []}
}
},
True,
True,
),
(
{
"_experiments": {
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": []}
}
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": []}
}
},
True,
Expand Down Expand Up @@ -1678,7 +1670,7 @@ async def hello(request):
pytest.param(
{
"send_default_pii": True,
"data_collection": None,
"data_collection": {},
},
{
"authorization": "[Filtered]",
Expand All @@ -1690,7 +1682,7 @@ async def hello(request):
pytest.param(
{
"send_default_pii": False,
"data_collection": None,
"data_collection": {},
},
{
"authorization": "[Filtered]",
Expand Down Expand Up @@ -1794,9 +1786,7 @@ async def test_sensitive_header_passthrough_with_pii_span_streaming(
traces_sample_rate=1.0,
send_default_pii=options["send_default_pii"],
trace_lifecycle="stream",
_experiments={
"data_collection": options["data_collection"],
},
data_collection=options["data_collection"],
)

async def hello(request):
Expand Down Expand Up @@ -2256,52 +2246,46 @@ async def hello(request):
id="defaults",
),
pytest.param(
{"_experiments": {"data_collection": {}}},
{"data_collection": {}},
"toy=tennisball&color=red&auth=%5BFiltered%5D",
id="data_collection_denylist_default",
),
pytest.param(
{
"_experiments": {
"data_collection": {
"url_query_params": {"mode": "denylist", "terms": ["toy"]}
}
"data_collection": {
"url_query_params": {"mode": "denylist", "terms": ["toy"]}
}
},
"toy=%5BFiltered%5D&color=red&auth=%5BFiltered%5D",
id="data_collection_denylist_custom_terms",
),
pytest.param(
{
"_experiments": {
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": ["toy"]}
}
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": ["toy"]}
}
},
"toy=tennisball&color=%5BFiltered%5D&auth=%5BFiltered%5D",
id="data_collection_allowlist",
),
pytest.param(
{
"_experiments": {
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": ["auth"]}
}
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": ["auth"]}
}
},
"toy=%5BFiltered%5D&color=%5BFiltered%5D&auth=%5BFiltered%5D",
id="data_collection_allowlist_sensitive_term",
),
pytest.param(
{"_experiments": {"data_collection": {"url_query_params": {"mode": "off"}}}},
{"data_collection": {"url_query_params": {"mode": "off"}}},
None,
id="data_collection_off",
),
pytest.param(
{
"send_default_pii": True,
"_experiments": {"data_collection": {"url_query_params": {"mode": "off"}}},
"data_collection": {"url_query_params": {"mode": "off"}},
},
None,
id="data_collection_wins_over_send_default_pii",
Expand Down Expand Up @@ -2422,7 +2406,7 @@ async def hello(request):
assert event["request"]["url"] == "http://{host}/".format(host=host)
assert event["request"]["method"] == "GET"

if "data_collection" not in init_kwargs.get("_experiments", {}):
if "data_collection" not in init_kwargs:
assert (
event["request"]["query_string"] == "toy=tennisball&color=red&auth=secret"
)
Expand Down
12 changes: 6 additions & 6 deletions tests/integrations/aiomysql/test_aiomysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ async def test_execute_many_record_params_with_data_collection_enabled(
) -> None:
sentry_init(
integrations=[AioMySQLIntegration()],
_experiments={"data_collection": {"database_query_data": True}},
data_collection={"database_query_data": True},
)
events = capture_events()

Expand Down Expand Up @@ -266,7 +266,7 @@ async def test_execute_many_record_params_with_data_collection_disabled(
) -> None:
sentry_init(
integrations=[AioMySQLIntegration(record_params=True)],
_experiments={"data_collection": {"database_query_data": False}},
data_collection={"database_query_data": False},
)
events = capture_events()

Expand Down Expand Up @@ -307,7 +307,7 @@ async def test_execute_many_record_params_with_data_collection_default(
) -> None:
sentry_init(
integrations=[AioMySQLIntegration()],
_experiments={"data_collection": {}},
data_collection={},
)
events = capture_events()

Expand Down Expand Up @@ -444,7 +444,7 @@ async def test_execute_record_params_with_data_collection_enabled(
) -> None:
sentry_init(
integrations=[AioMySQLIntegration()],
_experiments={"data_collection": {"database_query_data": True}},
data_collection={"database_query_data": True},
)
events = capture_events()

Expand Down Expand Up @@ -485,7 +485,7 @@ async def test_execute_record_params_with_data_collection_disabled(
) -> None:
sentry_init(
integrations=[AioMySQLIntegration(record_params=True)],
_experiments={"data_collection": {"database_query_data": False}},
data_collection={"database_query_data": False},
)
events = capture_events()

Expand Down Expand Up @@ -523,7 +523,7 @@ async def test_execute_record_params_with_data_collection_default(
) -> None:
sentry_init(
integrations=[AioMySQLIntegration()],
_experiments={"data_collection": {}},
data_collection={},
)
events = capture_events()

Expand Down
Loading
Loading