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
3 changes: 2 additions & 1 deletion MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
- The `data_collection` option is no longer accessible under `_experiments`. Use it as a top-level option instead.
- The experimental `record_sql_params` option was removed. Use the `database_query_data` setting of `data_collection` instead.
- The `record_params` option of `AsyncPGIntegration` and `AioMySQLIntegration` was removed. Use the `database_query_data` setting of `data_collection` instead.

- The `include_local_variables` option was removed. Use `data_collection`'s `stack_frame_variables` as a drop-in replacement.
- The `include_source_context` option was removed. Use `data_collection`'s `frame_context_lines` for more granular control over the source context reported by specifying the number of lines to include around the failing line, or set `frame_context_lines=0` to disable source context entirely.

## Deprecated

Expand Down
9 changes: 1 addition & 8 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,7 @@ def _record_lost_event(
# data_collection explicitly).
if not self.options["data_collection"]["provided_by_user"]:
self.options["data_collection"] = _map_from_send_default_pii(
send_default_pii=True,
include_local_variables=self.options["include_local_variables"]
is not False,
include_source_context=self.options["include_source_context"]
is not False,
send_default_pii=True
)

self.session_flusher = SessionFlusher(capture_func=_capture_envelope)
Expand Down Expand Up @@ -523,9 +519,6 @@ def _prepare_event(
"values": [
{
"stacktrace": current_stacktrace(
include_local_variables=self.options.get(
"include_local_variables", True
),
max_value_length=self.options.get(
"max_value_length", DEFAULT_MAX_VALUE_LENGTH
),
Expand Down
10 changes: 0 additions & 10 deletions sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -1378,8 +1378,6 @@ def __init__(
_experiments: "Experiments" = {}, # noqa: B006
proxy_headers: "Optional[Dict[str, str]]" = None,
project_root: "Optional[str]" = None,
include_local_variables: "Optional[bool]" = True,
include_source_context: "Optional[bool]" = True,
trace_propagation_targets: "Optional[Sequence[str]]" = [ # noqa: B006
MATCH_ALL
],
Expand Down Expand Up @@ -1516,14 +1514,6 @@ def __init__(

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

:param include_source_context: When enabled, source context will be included in events sent to Sentry.

This source context includes the five lines of code above and below the line of code where an error
happened.

:param include_local_variables: When enabled, the SDK will capture a snapshot of local variables to send with
the event to help with debugging.

:param add_full_stack: When capturing errors, Sentry stack traces typically only include frames that start the
moment an error occurs.

Expand Down
35 changes: 6 additions & 29 deletions sentry_sdk/data_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,7 @@ def _apply_key_value_collection_filtering(
return result


def _map_from_send_default_pii(
*,
send_default_pii: bool,
include_local_variables: bool,
include_source_context: bool,
) -> "DataCollection":
def _map_from_send_default_pii(*, send_default_pii: bool) -> "DataCollection":
"""
Build a fully-resolved ``DataCollection`` dict that mirrors the data
``send_default_pii`` collects today. Used when ``data_collection`` is not
Expand All @@ -179,10 +174,8 @@ def _map_from_send_default_pii(
"gen_ai": {"inputs": send_default_pii, "outputs": send_default_pii},
"database_query_data": send_default_pii,
"queues": send_default_pii,
"stack_frame_variables": include_local_variables,
"frame_context_lines": (
_DEFAULT_FRAME_CONTEXT_LINES if include_source_context else 0
),
"stack_frame_variables": True,
"frame_context_lines": _DEFAULT_FRAME_CONTEXT_LINES,
}


Expand Down Expand Up @@ -287,9 +280,8 @@ def _resolve_data_collection(options: "Dict[str, Any]") -> "DataCollection":
"""
Resolve the effective ``DataCollection`` dict from client ``options``.

Reads ``data_collection``, ``send_default_pii``, ``include_local_variables``
and ``include_source_context`` and returns a fully-resolved dict with
concrete values for every field.
Reads ``data_collection``, ``send_default_pii`` and returns a fully-resolved
dict with concrete values for every field.

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

Expand All @@ -305,17 +297,6 @@ def _resolve_data_collection(options: "Dict[str, Any]") -> "DataCollection":

send_default_pii = options.get("send_default_pii")

include_local_variables = (
bool(options.get("include_local_variables"))
if options.get("include_local_variables") is not None
else True
)
include_source_context = (
bool(options.get("include_source_context"))
if options.get("include_source_context") is not None
else True
)

if user_dc is not None:
if not isinstance(user_dc, dict):
raise TypeError(
Expand All @@ -332,8 +313,4 @@ def _resolve_data_collection(options: "Dict[str, Any]") -> "DataCollection":
user_dc,
)

return _map_from_send_default_pii(
send_default_pii=bool(send_default_pii),
include_local_variables=include_local_variables,
include_source_context=include_source_context,
)
return _map_from_send_default_pii(send_default_pii=bool(send_default_pii))
3 changes: 0 additions & 3 deletions sentry_sdk/integrations/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,6 @@ def _emit(self, record: "LogRecord") -> None:
"values": [
{
"stacktrace": current_stacktrace(
include_local_variables=client_options[
"include_local_variables"
],
max_value_length=client_options["max_value_length"],
),
"crashed": False,
Expand Down
62 changes: 18 additions & 44 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,8 +589,6 @@ def filename_for_module(
def serialize_frame(
frame: "FrameType",
tb_lineno: "Optional[int]" = None,
include_local_variables: bool = True,
include_source_context: bool = True,
max_value_length: "Optional[int]" = None,
custom_repr: "Optional[Callable[..., Optional[str]]]" = None,
) -> "Dict[str, Any]":
Expand Down Expand Up @@ -624,53 +622,38 @@ def serialize_frame(
"lineno": tb_lineno,
}

client_options = sentry_sdk.get_client().options
if has_data_collection_enabled(client_options):
include_source_context = bool(
client_options["data_collection"]["frame_context_lines"]
)
data_collection = sentry_sdk.get_client().options["data_collection"]

include_source_context = bool(data_collection["frame_context_lines"])
if include_source_context:
rv["pre_context"], rv["context_line"], rv["post_context"] = get_source_context(
frame, tb_lineno, max_value_length
)

if has_data_collection_enabled(client_options):
dc_stack_frame_vars_config = client_options["data_collection"][
"stack_frame_variables"
]
Comment thread
cursor[bot] marked this conversation as resolved.
dc_stack_frame_vars_config = data_collection["stack_frame_variables"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Frame serialization crashes without client

High Severity

serialize_frame now always reads data_collection from get_client(). When the SDK is not initialized, that client is a NonRecordingClient whose data_collection is None, so subscripting it raises TypeError. The old include_local_variables and include_source_context defaults no longer cover this path, so event_from_exception and exceptions_from_error_tuple can fail on any exception that has a traceback.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e46f50c. Configure here.


if isinstance(dc_stack_frame_vars_config, bool):
if dc_stack_frame_vars_config:
rv["vars"] = serialize(
dict(frame.f_locals), is_vars=True, custom_repr=custom_repr
)
else:
local_variables_to_send = _apply_key_value_collection_filtering(
items=dict(frame.f_locals),
behaviour=dc_stack_frame_vars_config,
if isinstance(dc_stack_frame_vars_config, bool):
if dc_stack_frame_vars_config:
rv["vars"] = serialize(
dict(frame.f_locals), is_vars=True, custom_repr=custom_repr
)
else:
local_variables_to_send = _apply_key_value_collection_filtering(
items=dict(frame.f_locals),
behaviour=dc_stack_frame_vars_config,
)

if local_variables_to_send:
serialized_variables = serialize(
local_variables_to_send, is_vars=True, custom_repr=custom_repr
)

rv["vars"] = serialized_variables
if local_variables_to_send:
serialized_variables = serialize(
local_variables_to_send, is_vars=True, custom_repr=custom_repr
)

elif include_local_variables:
rv["vars"] = serialize(
dict(frame.f_locals), is_vars=True, custom_repr=custom_repr
)
rv["vars"] = serialized_variables

return rv


def current_stacktrace(
include_local_variables: bool = True,
include_source_context: bool = True,
max_value_length: "Optional[int]" = None,
) -> "Dict[str, Any]":
def current_stacktrace(max_value_length: "Optional[int]" = None) -> "Dict[str, Any]":
__tracebackhide__ = True
frames = []

Expand All @@ -680,8 +663,6 @@ def current_stacktrace(
frames.append(
serialize_frame(
f,
include_local_variables=include_local_variables,
include_source_context=include_source_context,
max_value_length=max_value_length,
)
)
Expand Down Expand Up @@ -770,22 +751,16 @@ def single_exception_from_error_tuple(
exception_value["value"] = get_error_message(exc_value)

if client_options is None:
include_local_variables = True
include_source_context = True
max_value_length = None # fallback
custom_repr = None
else:
include_local_variables = client_options["include_local_variables"]
include_source_context = client_options["include_source_context"]
max_value_length = client_options["max_value_length"]
custom_repr = client_options.get("custom_repr")

frames: "List[Dict[str, Any]]" = [
serialize_frame(
tb.tb_frame,
tb_lineno=tb.tb_lineno,
include_local_variables=include_local_variables,
include_source_context=include_source_context,
max_value_length=max_value_length,
custom_repr=custom_repr,
)
Expand Down Expand Up @@ -1176,7 +1151,6 @@ def event_from_exception(

if client_options and client_options.get("add_full_stack", DEFAULT_ADD_FULL_STACK):
full_stack = current_stacktrace(
include_local_variables=client_options["include_local_variables"],
max_value_length=client_options["max_value_length"],
)["frames"]
else:
Expand Down
2 changes: 1 addition & 1 deletion tests/integrations/pure_eval/test_pure_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


@pytest.mark.parametrize("integrations", [[], [PureEvalIntegration()]])
def test_include_local_variables_enabled(sentry_init, capture_events, integrations):
def test_stack_frame_variables_enabled(sentry_init, capture_events, integrations):
sentry_init(
data_collection={"stack_frame_variables": True}, integrations=integrations
)
Expand Down
22 changes: 12 additions & 10 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,8 @@ def e(exc):
assert events[0]["exception"]["values"][0]["type"] == "ValueError"


def test_include_local_variables_enabled(sentry_init, capture_events):
sentry_init(include_local_variables=True)
def test_stack_frame_variables_enabled(sentry_init, capture_events):
sentry_init(data_collection={"stack_frame_variables": True})
events = capture_events()
try:
1 / 0
Expand All @@ -427,8 +427,8 @@ def test_include_local_variables_enabled(sentry_init, capture_events):
)


def test_include_local_variables_disabled(sentry_init, capture_events):
sentry_init(include_local_variables=False)
def test_stack_frame_variables_disabled(sentry_init, capture_events):
sentry_init(data_collection={"stack_frame_variables": False})
events = capture_events()
try:
1 / 0
Expand All @@ -443,8 +443,8 @@ def test_include_local_variables_disabled(sentry_init, capture_events):
)


def test_include_source_context_enabled(sentry_init, capture_events):
sentry_init(include_source_context=True)
def test_frame_source_context_enabled(sentry_init, capture_events):
sentry_init(data_collection={"frame_context_lines": 5})
events = capture_events()
try:
1 / 0
Expand All @@ -459,8 +459,8 @@ def test_include_source_context_enabled(sentry_init, capture_events):
assert "context_line" in frame


def test_include_source_context_disabled(sentry_init, capture_events):
sentry_init(include_source_context=False)
def test_frame_source_context_disabled(sentry_init, capture_events):
sentry_init(data_collection={"frame_context_lines": False})
events = capture_events()
try:
1 / 0
Expand Down Expand Up @@ -542,8 +542,10 @@ def bar():
assert functions[-2:] == ["foo", "bar"]


def test_attach_stacktrace_enabled_no_locals(sentry_init, capture_events):
sentry_init(attach_stacktrace=True, include_local_variables=False)
def test_attach_stacktrace_enabled_no_stack_trace_vars(sentry_init, capture_events):
sentry_init(
attach_stacktrace=True, data_collection={"stack_frame_variables": False}
)
events = capture_events()

def foo():
Expand Down
19 changes: 0 additions & 19 deletions tests/test_data_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,6 @@ def _get(dc, path):
},
id="explicit_partial_fills_omitted_with_spec_defaults",
),
pytest.param(
{
"data_collection": {},
"include_local_variables": False,
"include_source_context": False,
},
{"stack_frame_variables": True, "frame_context_lines": 5},
id="explicit_data_collection_ignores_legacy_include_local_variables",
),
pytest.param(
{
"data_collection": {
Expand Down Expand Up @@ -311,16 +302,6 @@ def _get(dc, path):
{"frame_context_lines": 5},
id="frame_context_lines_none_falls_back_to_spec_default",
),
pytest.param(
{"include_local_variables": False, "include_source_context": False},
{"stack_frame_variables": False, "frame_context_lines": 0},
id="legacy_include_local_variables_off_disables_stack_frame_variables",
),
pytest.param(
{"include_local_variables": True, "include_source_context": True},
{"stack_frame_variables": True, "frame_context_lines": 5},
id="legacy_include_local_variables_on_enables_stack_frame_variables",
),
],
)
def test_initialize_client_data_collection(sentry_init, options, expected):
Expand Down
Loading
Loading