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
9 changes: 2 additions & 7 deletions sentry_sdk/data_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,22 +191,18 @@ def _map_from_send_default_pii(
def _resolve_explicit(
d: "dict[str, Any]",
include_local_variables: bool,
include_source_context: bool,
) -> "DataCollection":
"""
Build a fully-resolved ``DataCollection`` from a user-supplied
``data_collection`` dict, filling in spec defaults for any omitted or
partially-specified field. Frame fields fall back to the legacy
``include_local_variables`` / ``include_source_context`` options when unset.
partially-specified field.
"""
# frame_context_lines accepts an integer or a boolean fallback (spec: True
# -> platform default of 5, False -> 0). bool is a subclass of int, so
# coerce explicitly before treating it as a line count.
frame_context_lines = d.get("frame_context_lines")
if frame_context_lines is None:
frame_context_lines = (
_DEFAULT_FRAME_CONTEXT_LINES if include_source_context else 0
)
frame_context_lines = _DEFAULT_FRAME_CONTEXT_LINES
elif isinstance(frame_context_lines, bool):
frame_context_lines = _DEFAULT_FRAME_CONTEXT_LINES if frame_context_lines else 0

Expand Down Expand Up @@ -324,7 +320,6 @@ def _resolve_data_collection(options: "Dict[str, Any]") -> "DataCollection":
return _resolve_explicit(
user_dc,
include_local_variables,
include_source_context,
)

return _map_from_send_default_pii(
Expand Down
13 changes: 13 additions & 0 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,14 @@ def get_lines_from_file(
loader: "Optional[Any]" = None,
module: "Optional[str]" = None,
) -> "Tuple[List[Annotated[str]], Optional[Annotated[str]], List[Annotated[str]]]":
client_options = sentry_sdk.get_client().options

# This is the default pre-data collection. Should be removed once data collection
# is fully released
context_lines = 5
if has_data_collection_enabled(client_options):
context_lines = client_options["data_collection"]["frame_context_lines"]

source = None
if loader is not None and hasattr(loader, "get_source"):
try:
Expand Down Expand Up @@ -607,6 +614,12 @@ 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"]
)

if include_source_context:
rv["pre_context"], rv["context_line"], rv["post_context"] = get_source_context(
frame, tb_lineno, max_value_length
Expand Down
4 changes: 2 additions & 2 deletions tests/test_data_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ def _get(dc, path):
"include_local_variables": False,
"include_source_context": False,
},
{"stack_frame_variables": False, "frame_context_lines": 0},
id="explicit_frame_fields_fall_back_to_legacy_options",
{"stack_frame_variables": False, "frame_context_lines": 5},
id="explicit_stack_frame_variables_falls_back_to_legacy_option",
),
pytest.param(
{
Expand Down
84 changes: 78 additions & 6 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,16 +492,41 @@ def test_warns_on_invalid_sample_rate(rate, StringContaining): # noqa: N803


@pytest.mark.parametrize(
"include_source_context",
[True, False],
"options,include_source_context,expected_source_context",
[
pytest.param({}, True, True, id="no_data_collection-include_true"),
pytest.param({}, False, False, id="no_data_collection-include_false"),
pytest.param(
{"_experiments": {"data_collection": {}}},
False,
True,
id="data_collection-spec_default_overrides_include_false",
),
pytest.param(
{"_experiments": {"data_collection": {"frame_context_lines": 3}}},
True,
True,
id="data_collection-frame_context_lines_3",
),
pytest.param(
{"_experiments": {"data_collection": {"frame_context_lines": 0}}},
True,
False,
id="data_collection-frame_context_lines_0_overrides_include_true",
),
],
)
def test_include_source_context_when_serializing_frame(include_source_context):
def test_include_source_context_when_serializing_frame(
sentry_init, options, include_source_context, expected_source_context
):
sentry_init(**options)

frame = sys._getframe()
result = serialize_frame(frame, include_source_context=include_source_context)

assert include_source_context ^ ("pre_context" in result) ^ True
assert include_source_context ^ ("context_line" in result) ^ True
assert include_source_context ^ ("post_context" in result) ^ True
assert ("pre_context" in result) is expected_source_context
assert ("context_line" in result) is expected_source_context
assert ("post_context" in result) is expected_source_context


@pytest.mark.parametrize(
Expand Down Expand Up @@ -1069,6 +1094,53 @@ def fake_getlines(filename):
assert result == expected_result


@pytest.mark.parametrize(
"options,expected_context_lines",
[
pytest.param({}, 5, id="no_data_collection-defaults_to_5"),
pytest.param(
{"_experiments": {"data_collection": {}}},
5,
id="data_collection-spec_default_5",
),
pytest.param(
{"_experiments": {"data_collection": {"frame_context_lines": 3}}},
3,
id="data_collection-frame_context_lines_3",
),
pytest.param(
{"_experiments": {"data_collection": {"frame_context_lines": 0}}},
0,
id="data_collection-frame_context_lines_0",
),
pytest.param(
{
"_experiments": {"data_collection": {}},
"include_source_context": False,
},
5,
id="data_collection-spec_default_overrides_include_source_context_false",
),
],
)
def test_get_lines_from_file_frame_context_lines(
sentry_init, options, expected_context_lines
):
source = ["line{}\n".format(i) for i in range(20)]

sentry_init(**options)

def fake_getlines(filename):
return source

with mock.patch("sentry_sdk.utils.linecache.getlines", fake_getlines):
pre_context, context_line, post_context = get_lines_from_file("filename", 10)

assert context_line == "line10"
assert len(pre_context) == expected_context_lines
assert len(post_context) == expected_context_lines


def test_safe_serialize_plain_string():
assert safe_serialize("already a string") == "already a string"

Expand Down
Loading