|
23 | 23 | VERSION, |
24 | 24 | ClientConstructor, |
25 | 25 | ) |
| 26 | +from sentry_sdk.data_collection import ( |
| 27 | + OFF_DATA_COLLECTION, |
| 28 | + DataCollection, |
| 29 | + _map_from_send_default_pii, |
| 30 | + resolve_data_collection, |
| 31 | +) |
26 | 32 | from sentry_sdk.envelope import Envelope, Item, PayloadRef |
27 | 33 | from sentry_sdk.integrations import _DEFAULT_INTEGRATIONS, setup_integrations |
28 | 34 | from sentry_sdk.integrations.dedupe import DedupeIntegration |
@@ -345,11 +351,11 @@ def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]": |
345 | 351 | if rv["enable_tracing"] is True and rv["traces_sample_rate"] is None: |
346 | 352 | rv["traces_sample_rate"] = 1.0 |
347 | 353 |
|
| 354 | + rv["data_collection"] = resolve_data_collection(rv) |
| 355 | + |
348 | 356 | if rv["event_scrubber"] is None: |
349 | 357 | 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 |
353 | 359 | ) |
354 | 360 |
|
355 | 361 | if rv["socket_options"] and not isinstance(rv["socket_options"], list): |
@@ -425,6 +431,23 @@ def parsed_dsn(self) -> "Optional[Dsn]": |
425 | 431 | def should_send_default_pii(self) -> bool: |
426 | 432 | return False |
427 | 433 |
|
| 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 | + |
428 | 451 | def is_active(self) -> bool: |
429 | 452 | """ |
430 | 453 | .. versionadded:: 2.0.0 |
@@ -614,6 +637,17 @@ def _record_lost_event( |
614 | 637 | self.options["error_sampler"] = sample_all |
615 | 638 | self.options["traces_sampler"] = sample_all |
616 | 639 | 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 | + ) |
617 | 651 |
|
618 | 652 | self.session_flusher = SessionFlusher(capture_func=_capture_envelope) |
619 | 653 |
|
@@ -724,6 +758,59 @@ def should_send_default_pii(self) -> bool: |
724 | 758 | """ |
725 | 759 | return self.options.get("send_default_pii") or False |
726 | 760 |
|
| 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 | + |
727 | 814 | @property |
728 | 815 | def dsn(self) -> "Optional[str]": |
729 | 816 | """Returns the configured DSN as string.""" |
|
0 commit comments