Skip to content

Commit 5478df2

Browse files
puittenbroekPeter Uittenbroeksentrivanaantonpirker
authored
Read MAX_VALUE_LENGTH from client options (#2121) (#2171)
--------- Co-authored-by: Peter Uittenbroek <[email protected]> Co-authored-by: Ivana Kellyerova <[email protected]> Co-authored-by: Anton Pirker <[email protected]>
1 parent 2b1d1cc commit 5478df2

File tree

9 files changed

+98
-28
lines changed

9 files changed

+98
-28
lines changed

sentry_sdk/client.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from sentry_sdk.tracing import trace, has_tracing_enabled
2222
from sentry_sdk.transport import make_transport
2323
from sentry_sdk.consts import (
24+
DEFAULT_MAX_VALUE_LENGTH,
2425
DEFAULT_OPTIONS,
2526
INSTRUMENTER,
2627
VERSION,
@@ -304,7 +305,12 @@ def _prepare_event(
304305
"values": [
305306
{
306307
"stacktrace": current_stacktrace(
307-
self.options["include_local_variables"]
308+
include_local_variables=self.options.get(
309+
"include_local_variables", True
310+
),
311+
max_value_length=self.options.get(
312+
"max_value_length", DEFAULT_MAX_VALUE_LENGTH
313+
),
308314
),
309315
"crashed": False,
310316
"current": True,
@@ -339,7 +345,9 @@ def _prepare_event(
339345
# generally not surface in before_send
340346
if event is not None:
341347
event = serialize(
342-
event, max_request_body_size=self.options.get("max_request_body_size")
348+
event,
349+
max_request_body_size=self.options.get("max_request_body_size"),
350+
max_value_length=self.options.get("max_value_length"),
343351
)
344352

345353
before_send = self.options["before_send"]

sentry_sdk/consts.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from sentry_sdk._types import TYPE_CHECKING
22

3+
# up top to prevent circular import due to integration import
4+
DEFAULT_MAX_VALUE_LENGTH = 1024
5+
36
if TYPE_CHECKING:
47
import sentry_sdk
58

@@ -43,7 +46,6 @@
4346

4447
DEFAULT_QUEUE_SIZE = 100
4548
DEFAULT_MAX_BREADCRUMBS = 100
46-
4749
MATCH_ALL = r".*"
4850

4951
FALSE_VALUES = [
@@ -206,6 +208,7 @@ def __init__(
206208
], # type: Optional[Sequence[str]]
207209
functions_to_trace=[], # type: Sequence[Dict[str, str]] # noqa: B006
208210
event_scrubber=None, # type: Optional[sentry_sdk.scrubber.EventScrubber]
211+
max_value_length=DEFAULT_MAX_VALUE_LENGTH, # type: int
209212
):
210213
# type: (...) -> None
211214
pass

sentry_sdk/integrations/logging.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,10 @@ def _emit(self, record):
205205
"values": [
206206
{
207207
"stacktrace": current_stacktrace(
208-
client_options["include_local_variables"]
208+
include_local_variables=client_options[
209+
"include_local_variables"
210+
],
211+
max_value_length=client_options["max_value_length"],
209212
),
210213
"crashed": False,
211214
"current": True,

sentry_sdk/serializer.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def serialize(event, **kwargs):
123123
keep_request_bodies = (
124124
kwargs.pop("max_request_body_size", None) == "always"
125125
) # type: bool
126+
max_value_length = kwargs.pop("max_value_length", None) # type: Optional[int]
126127

127128
def _annotate(**meta):
128129
# type: (**Any) -> None
@@ -295,7 +296,9 @@ def _serialize_node_impl(
295296
if remaining_depth is not None and remaining_depth <= 0:
296297
_annotate(rem=[["!limit", "x"]])
297298
if is_databag:
298-
return _flatten_annotated(strip_string(safe_repr(obj)))
299+
return _flatten_annotated(
300+
strip_string(safe_repr(obj), max_length=max_value_length)
301+
)
299302
return None
300303

301304
if is_databag and global_repr_processors:
@@ -396,7 +399,7 @@ def _serialize_node_impl(
396399
if is_span_description:
397400
return obj
398401

399-
return _flatten_annotated(strip_string(obj))
402+
return _flatten_annotated(strip_string(obj, max_length=max_value_length))
400403

401404
#
402405
# Start of serialize() function

sentry_sdk/utils.py

+30-16
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import sentry_sdk
5151
from sentry_sdk._compat import PY2, PY33, PY37, implements_str, text_type, urlparse
5252
from sentry_sdk._types import TYPE_CHECKING
53+
from sentry_sdk.consts import DEFAULT_MAX_VALUE_LENGTH
5354

5455
if TYPE_CHECKING:
5556
from types import FrameType, TracebackType
@@ -75,7 +76,7 @@
7576
# The logger is created here but initialized in the debug support module
7677
logger = logging.getLogger("sentry_sdk.errors")
7778

78-
MAX_STRING_LENGTH = 1024
79+
7980
BASE64_ALPHABET = re.compile(r"^[a-zA-Z0-9/+=]*$")
8081

8182
SENSITIVE_DATA_SUBSTITUTE = "[Filtered]"
@@ -468,6 +469,7 @@ def iter_stacks(tb):
468469
def get_lines_from_file(
469470
filename, # type: str
470471
lineno, # type: int
472+
max_length=None, # type: Optional[int]
471473
loader=None, # type: Optional[Any]
472474
module=None, # type: Optional[str]
473475
):
@@ -496,11 +498,12 @@ def get_lines_from_file(
496498

497499
try:
498500
pre_context = [
499-
strip_string(line.strip("\r\n")) for line in source[lower_bound:lineno]
501+
strip_string(line.strip("\r\n"), max_length=max_length)
502+
for line in source[lower_bound:lineno]
500503
]
501-
context_line = strip_string(source[lineno].strip("\r\n"))
504+
context_line = strip_string(source[lineno].strip("\r\n"), max_length=max_length)
502505
post_context = [
503-
strip_string(line.strip("\r\n"))
506+
strip_string(line.strip("\r\n"), max_length=max_length)
504507
for line in source[(lineno + 1) : upper_bound]
505508
]
506509
return pre_context, context_line, post_context
@@ -512,6 +515,7 @@ def get_lines_from_file(
512515
def get_source_context(
513516
frame, # type: FrameType
514517
tb_lineno, # type: int
518+
max_value_length=None, # type: Optional[int]
515519
):
516520
# type: (...) -> Tuple[List[Annotated[str]], Optional[Annotated[str]], List[Annotated[str]]]
517521
try:
@@ -528,7 +532,9 @@ def get_source_context(
528532
loader = None
529533
lineno = tb_lineno - 1
530534
if lineno is not None and abs_path:
531-
return get_lines_from_file(abs_path, lineno, loader, module)
535+
return get_lines_from_file(
536+
abs_path, lineno, max_value_length, loader=loader, module=module
537+
)
532538
return [], None, []
533539

534540

@@ -602,9 +608,13 @@ def filename_for_module(module, abs_path):
602608

603609

604610
def serialize_frame(
605-
frame, tb_lineno=None, include_local_variables=True, include_source_context=True
611+
frame,
612+
tb_lineno=None,
613+
include_local_variables=True,
614+
include_source_context=True,
615+
max_value_length=None,
606616
):
607-
# type: (FrameType, Optional[int], bool, bool) -> Dict[str, Any]
617+
# type: (FrameType, Optional[int], bool, bool, Optional[int]) -> Dict[str, Any]
608618
f_code = getattr(frame, "f_code", None)
609619
if not f_code:
610620
abs_path = None
@@ -630,7 +640,7 @@ def serialize_frame(
630640

631641
if include_source_context:
632642
rv["pre_context"], rv["context_line"], rv["post_context"] = get_source_context(
633-
frame, tb_lineno
643+
frame, tb_lineno, max_value_length
634644
)
635645

636646
if include_local_variables:
@@ -639,8 +649,12 @@ def serialize_frame(
639649
return rv
640650

641651

642-
def current_stacktrace(include_local_variables=True, include_source_context=True):
643-
# type: (bool, bool) -> Any
652+
def current_stacktrace(
653+
include_local_variables=True, # type: bool
654+
include_source_context=True, # type: bool
655+
max_value_length=None, # type: Optional[int]
656+
):
657+
# type: (...) -> Dict[str, Any]
644658
__tracebackhide__ = True
645659
frames = []
646660

@@ -652,6 +666,7 @@ def current_stacktrace(include_local_variables=True, include_source_context=True
652666
f,
653667
include_local_variables=include_local_variables,
654668
include_source_context=include_source_context,
669+
max_value_length=max_value_length,
655670
)
656671
)
657672
f = f.f_back
@@ -724,16 +739,19 @@ def single_exception_from_error_tuple(
724739
if client_options is None:
725740
include_local_variables = True
726741
include_source_context = True
742+
max_value_length = DEFAULT_MAX_VALUE_LENGTH # fallback
727743
else:
728744
include_local_variables = client_options["include_local_variables"]
729745
include_source_context = client_options["include_source_context"]
746+
max_value_length = client_options["max_value_length"]
730747

731748
frames = [
732749
serialize_frame(
733750
tb.tb_frame,
734751
tb_lineno=tb.tb_lineno,
735752
include_local_variables=include_local_variables,
736753
include_source_context=include_source_context,
754+
max_value_length=max_value_length,
737755
)
738756
for tb in iter_stacks(tb)
739757
]
@@ -819,9 +837,7 @@ def exceptions_from_error(
819837
parent_id = exception_id
820838
exception_id += 1
821839

822-
should_supress_context = (
823-
hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore
824-
)
840+
should_supress_context = hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore
825841
if should_supress_context:
826842
# Add direct cause.
827843
# The field `__cause__` is set when raised with the exception (using the `from` keyword).
@@ -1082,13 +1098,11 @@ def _is_in_project_root(abs_path, project_root):
10821098

10831099
def strip_string(value, max_length=None):
10841100
# type: (str, Optional[int]) -> Union[AnnotatedValue, str]
1085-
# TODO: read max_length from config
10861101
if not value:
10871102
return value
10881103

10891104
if max_length is None:
1090-
# This is intentionally not just the default such that one can patch `MAX_STRING_LENGTH` and affect `strip_string`.
1091-
max_length = MAX_STRING_LENGTH
1105+
max_length = DEFAULT_MAX_VALUE_LENGTH
10921106

10931107
length = len(value.encode("utf-8"))
10941108

tests/integrations/sqlalchemy/test_sqlalchemy.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
from sqlalchemy import text
99

1010
from sentry_sdk import capture_message, start_transaction, configure_scope
11-
from sentry_sdk.consts import SPANDATA
11+
from sentry_sdk.consts import DEFAULT_MAX_VALUE_LENGTH, SPANDATA
1212
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
1313
from sentry_sdk.serializer import MAX_EVENT_BYTES
14-
from sentry_sdk.utils import json_dumps, MAX_STRING_LENGTH
14+
from sentry_sdk.utils import json_dumps
1515

1616

1717
def test_orm_queries(sentry_init, capture_events):
@@ -168,7 +168,7 @@ def test_large_event_not_truncated(sentry_init, capture_events):
168168
)
169169
events = capture_events()
170170

171-
long_str = "x" * (MAX_STRING_LENGTH + 10)
171+
long_str = "x" * (DEFAULT_MAX_VALUE_LENGTH + 10)
172172

173173
with configure_scope() as scope:
174174

@@ -204,7 +204,7 @@ def processor(event, hint):
204204
assert description.endswith("SELECT 98 UNION SELECT 99")
205205

206206
# Smoke check that truncation of other fields has not changed.
207-
assert len(event["message"]) == MAX_STRING_LENGTH
207+
assert len(event["message"]) == DEFAULT_MAX_VALUE_LENGTH
208208

209209
# The _meta for other truncated fields should be there as well.
210210
assert event["_meta"]["message"] == {

tests/test_client.py

+19-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from sentry_sdk.utils import HAS_CHAINED_EXCEPTIONS
2525
from sentry_sdk.utils import logger
2626
from sentry_sdk.serializer import MAX_DATABAG_BREADTH
27-
from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS
27+
from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, DEFAULT_MAX_VALUE_LENGTH
2828

2929
try:
3030
from unittest import mock # python 3.3 and above
@@ -1118,3 +1118,21 @@ def test_multiple_positional_args(sentry_init):
11181118
with pytest.raises(TypeError) as exinfo:
11191119
sentry_init(1, None)
11201120
assert "Only single positional argument is expected" in str(exinfo.value)
1121+
1122+
1123+
@pytest.mark.parametrize(
1124+
"sdk_options, expected_data_length",
1125+
[
1126+
({}, DEFAULT_MAX_VALUE_LENGTH),
1127+
({"max_value_length": 1800}, 1800),
1128+
],
1129+
)
1130+
def test_max_value_length_option(
1131+
sentry_init, capture_events, sdk_options, expected_data_length
1132+
):
1133+
sentry_init(sdk_options)
1134+
events = capture_events()
1135+
1136+
capture_message("a" * 2000)
1137+
1138+
assert len(events[0]["message"]) == expected_data_length

tests/test_exceptiongroup.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def test_exceptiongroup():
4747
client_options={
4848
"include_local_variables": True,
4949
"include_source_context": True,
50+
"max_value_length": 1024,
5051
},
5152
mechanism={"type": "test_suite", "handled": False},
5253
)
@@ -162,6 +163,7 @@ def test_exceptiongroup_simple():
162163
client_options={
163164
"include_local_variables": True,
164165
"include_source_context": True,
166+
"max_value_length": 1024,
165167
},
166168
mechanism={"type": "test_suite", "handled": False},
167169
)
@@ -190,7 +192,6 @@ def test_exceptiongroup_simple():
190192
}
191193
frame = exception_values[1]["stacktrace"]["frames"][0]
192194
assert frame["module"] == "tests.test_exceptiongroup"
193-
assert frame["lineno"] == 151
194195
assert frame["context_line"] == " raise ExceptionGroup("
195196

196197

@@ -207,6 +208,7 @@ def test_exception_chain_cause():
207208
client_options={
208209
"include_local_variables": True,
209210
"include_source_context": True,
211+
"max_value_length": 1024,
210212
},
211213
mechanism={"type": "test_suite", "handled": False},
212214
)
@@ -246,6 +248,7 @@ def test_exception_chain_context():
246248
client_options={
247249
"include_local_variables": True,
248250
"include_source_context": True,
251+
"max_value_length": 1024,
249252
},
250253
mechanism={"type": "test_suite", "handled": False},
251254
)
@@ -284,6 +287,7 @@ def test_simple_exception():
284287
client_options={
285288
"include_local_variables": True,
286289
"include_source_context": True,
290+
"max_value_length": 1024,
287291
},
288292
mechanism={"type": "test_suite", "handled": False},
289293
)

tests/test_serializer.py

+17
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,20 @@ def test_no_trimming_if_max_request_body_size_is_always(body_normalizer):
144144
result = body_normalizer(data, max_request_body_size="always")
145145

146146
assert result == data
147+
148+
149+
def test_max_value_length_default(body_normalizer):
150+
data = {"key": "a" * 2000}
151+
152+
result = body_normalizer(data)
153+
154+
assert len(result["key"]) == 1024 # fallback max length
155+
156+
157+
def test_max_value_length(body_normalizer):
158+
data = {"key": "a" * 2000}
159+
160+
max_value_length = 1800
161+
result = body_normalizer(data, max_value_length=max_value_length)
162+
163+
assert len(result["key"]) == max_value_length

0 commit comments

Comments
 (0)