Skip to content

Commit 98022a4

Browse files
committed
feat(data-collection): add data_collection option, resolution, and accessors
1 parent 7787e6d commit 98022a4

8 files changed

Lines changed: 1124 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Add the `data_collection` option, a structured configuration that supersedes `send_default_pii` for controlling what data integrations collect automatically (user identity, cookies, HTTP headers, query params, HTTP bodies, generative AI inputs/outputs, stack frame variables, source context). See the [Data Collection spec](https://develop.sentry.dev/sdk/foundations/client/data-collection/).
8+
- Adds `sentry_sdk.DataCollection`, `KeyValueCollectionBehavior`, `HttpHeadersCollection`, and `GenAICollection`.
9+
- When `data_collection` is not set, behavior is derived from `send_default_pii` (now deprecated), so upgrading without configuring `data_collection` changes nothing.
10+
- `frame_context_lines` is now configurable (previously hardcoded to 5); AI integrations' `include_prompts` becomes a per-integration override of `data_collection.gen_ai`.
11+
312
## 2.63.0
413

514
### Bug Fixes 🐛

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ sentry_sdk.init(
4949
# Set traces_sample_rate to 1.0 to capture 100%
5050
# of traces for performance monitoring.
5151
traces_sample_rate=1.0,
52+
53+
# To disable sending user data and HTTP request/response bodies, uncomment
54+
# the line below. For more info visit:
55+
# https://docs.sentry.io/platforms/python/configuration/options/#data_collection
56+
# data_collection=sentry_sdk.DataCollection(user_info=False, http_bodies=[]),
5257
)
5358
```
5459

sentry_sdk/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
from sentry_sdk.scope import Scope # isort: skip
44
from sentry_sdk.client import Client # isort: skip
5+
from sentry_sdk.data_collection import ( # isort: skip
6+
DataCollection,
7+
GenAICollection,
8+
HttpHeadersCollection,
9+
KeyValueCollectionBehavior,
10+
)
511
from sentry_sdk.consts import VERSION
612
from sentry_sdk.transport import HttpTransport, Transport
713

@@ -11,6 +17,10 @@
1117
"Hub",
1218
"Scope",
1319
"Client",
20+
"DataCollection",
21+
"GenAICollection",
22+
"HttpHeadersCollection",
23+
"KeyValueCollectionBehavior",
1424
"Transport",
1525
"HttpTransport",
1626
"VERSION",

sentry_sdk/client.py

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@
2323
VERSION,
2424
ClientConstructor,
2525
)
26+
from sentry_sdk.data_collection import (
27+
OFF_DATA_COLLECTION,
28+
DataCollection,
29+
_map_from_send_default_pii,
30+
resolve_data_collection,
31+
)
2632
from sentry_sdk.envelope import Envelope, Item, PayloadRef
2733
from sentry_sdk.integrations import _DEFAULT_INTEGRATIONS, setup_integrations
2834
from sentry_sdk.integrations.dedupe import DedupeIntegration
@@ -345,11 +351,11 @@ def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]":
345351
if rv["enable_tracing"] is True and rv["traces_sample_rate"] is None:
346352
rv["traces_sample_rate"] = 1.0
347353

354+
rv["data_collection"] = resolve_data_collection(rv)
355+
348356
if rv["event_scrubber"] is None:
349357
rv["event_scrubber"] = EventScrubber(
350-
send_default_pii=(
351-
False if rv["send_default_pii"] is None else rv["send_default_pii"]
352-
)
358+
send_default_pii=rv["data_collection"].user_info
353359
)
354360

355361
if rv["socket_options"] and not isinstance(rv["socket_options"], list):
@@ -425,6 +431,23 @@ def parsed_dsn(self) -> "Optional[Dsn]":
425431
def should_send_default_pii(self) -> bool:
426432
return False
427433

434+
@property
435+
def data_collection(self) -> "DataCollection":
436+
return OFF_DATA_COLLECTION
437+
438+
def should_collect_user_info(self) -> bool:
439+
return False
440+
441+
def should_collect_gen_ai_inputs(
442+
self, include_prompts: "Optional[bool]" = None
443+
) -> bool:
444+
return False
445+
446+
def should_collect_gen_ai_outputs(
447+
self, include_prompts: "Optional[bool]" = None
448+
) -> bool:
449+
return False
450+
428451
def is_active(self) -> bool:
429452
"""
430453
.. versionadded:: 2.0.0
@@ -614,6 +637,17 @@ def _record_lost_event(
614637
self.options["error_sampler"] = sample_all
615638
self.options["traces_sampler"] = sample_all
616639
self.options["profiles_sampler"] = sample_all
640+
# data_collection was resolved in _get_options() before this
641+
# spotlight override flipped send_default_pii on. Re-derive it so
642+
# the should_collect_* accessors agree with should_send_default_pii()
643+
# in DSN-less spotlight mode (only when the user did not set
644+
# data_collection explicitly).
645+
if not self.options["data_collection"].explicit:
646+
self.options["data_collection"] = _map_from_send_default_pii(
647+
True,
648+
self.options["include_local_variables"] is not False,
649+
self.options["include_source_context"] is not False,
650+
)
617651

618652
self.session_flusher = SessionFlusher(capture_func=_capture_envelope)
619653

@@ -724,6 +758,59 @@ def should_send_default_pii(self) -> bool:
724758
"""
725759
return self.options.get("send_default_pii") or False
726760

761+
@property
762+
def data_collection(self) -> "DataCollection":
763+
"""
764+
Returns the resolved :class:`~sentry_sdk.data_collection.DataCollection`
765+
config for this client.
766+
"""
767+
dc = self.options.get("data_collection")
768+
return dc if dc is not None else OFF_DATA_COLLECTION
769+
770+
def should_collect_user_info(self) -> bool:
771+
"""
772+
Returns whether the SDK should automatically populate ``user.*`` fields
773+
(id, email, username, ip_address) from instrumentation.
774+
"""
775+
return bool(self.data_collection.user_info)
776+
777+
def should_collect_gen_ai_inputs(
778+
self, include_prompts: "Optional[bool]" = None
779+
) -> bool:
780+
"""
781+
Returns whether the SDK should collect generative AI input content.
782+
783+
``include_prompts`` is the integration-level override (if set, it takes
784+
precedence over the global ``data_collection.gen_ai.inputs`` setting).
785+
"""
786+
return self._should_collect_gen_ai_content("inputs", include_prompts)
787+
788+
def should_collect_gen_ai_outputs(
789+
self, include_prompts: "Optional[bool]" = None
790+
) -> bool:
791+
"""
792+
Returns whether the SDK should collect generative AI output content.
793+
794+
``include_prompts`` is the integration-level override (if set, it takes
795+
precedence over the global ``data_collection.gen_ai.outputs`` setting).
796+
"""
797+
return self._should_collect_gen_ai_content("outputs", include_prompts)
798+
799+
def _should_collect_gen_ai_content(
800+
self, direction: str, include_prompts: "Optional[bool]"
801+
) -> bool:
802+
dc = self.data_collection
803+
if dc.explicit:
804+
# Integration-level override wins over the global gen_ai setting.
805+
if include_prompts is not None:
806+
return include_prompts
807+
return bool(getattr(dc.gen_ai, direction))
808+
# Legacy (data_collection not set): preserve the historical gate
809+
# `should_send_default_pii() and integration.include_prompts`.
810+
# `include_prompts is None` means "no integration-level override", which
811+
# falls back to the legacy default of True (collect when PII is on).
812+
return self.should_send_default_pii() and (include_prompts is not False)
813+
727814
@property
728815
def dsn(self) -> "Optional[str]":
729816
"""Returns the configured DSN as string."""

sentry_sdk/consts.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class CompressionAlgo(Enum):
5656
TracesSampler,
5757
TransactionProcessor,
5858
)
59+
from sentry_sdk.data_collection import DataCollection
5960

6061
# Experiments are feature flags to enable and disable certain unstable SDK
6162
# functionality. Changing them from the defaults (`None`) in production
@@ -1272,6 +1273,7 @@ def __init__(
12721273
transport_queue_size: int = DEFAULT_QUEUE_SIZE,
12731274
sample_rate: float = 1.0,
12741275
send_default_pii: "Optional[bool]" = None,
1276+
data_collection: "Optional[Union[DataCollection, Dict[str, Any]]]" = None,
12751277
http_proxy: "Optional[str]" = None,
12761278
https_proxy: "Optional[str]" = None,
12771279
ignore_errors: "Sequence[Union[type, str]]" = [], # noqa: B006
@@ -1426,6 +1428,26 @@ def __init__(
14261428
If you enable this option, be sure to manually remove what you don't want to send using our features for
14271429
managing `Sensitive Data <https://docs.sentry.io/data-management/sensitive-data/>`_.
14281430
1431+
.. deprecated::
1432+
Use `data_collection` instead. `send_default_pii` is still honored when `data_collection` is not set.
1433+
1434+
:param data_collection: Structured configuration controlling what data integrations collect automatically,
1435+
superseding `send_default_pii`. Pass a dict or a :class:`sentry_sdk.DataCollection` instance to enable or
1436+
restrict collection per category (user identity, cookies, HTTP headers/bodies, query params, generative AI
1437+
inputs/outputs, stack frame variables, source context).
1438+
1439+
When `data_collection` is set, omitted fields use their defaults (most categories are collected, with the
1440+
sensitive denylist scrubbing values). When it is not set, the SDK derives behavior from `send_default_pii`
1441+
so that upgrading without configuring `data_collection` changes nothing. If both are set, `data_collection`
1442+
takes precedence.
1443+
1444+
Example::
1445+
1446+
sentry_sdk.init(
1447+
dsn="...",
1448+
data_collection={"user_info": False, "http_bodies": []},
1449+
)
1450+
14291451
:param event_scrubber: Scrubs the event payload for sensitive information such as cookies, sessions, and
14301452
passwords from a `denylist`.
14311453

0 commit comments

Comments
 (0)