Skip to content

Read MAX_VALUE_LENGTH from client options (#2121) #2171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2580126
Read MAX_STRING_LENGTH from client options (#2121)
Jun 14, 2023
b775430
Some more testing and passing max_string_length
Jun 14, 2023
c2d47f1
Use kwargs more
Jun 14, 2023
e96d146
fix mypy issues
Jun 16, 2023
34c1a5f
fix mypy issues and test_sqlalchemy
Jun 16, 2023
ec33d83
Merge branch 'master' into max-string-length-from-client-options-2121
puittenbroek Jun 19, 2023
2e850b7
Merge branch 'master' into max-string-length-from-client-options-2121
puittenbroek Jun 21, 2023
d7069a7
Merge branch 'master' into max-string-length-from-client-options-2121
sentrivana Jun 22, 2023
8470996
Merge branch 'master' into max-string-length-from-client-options-2121
sentrivana Jun 23, 2023
80edf12
Merge branch 'master' into max-string-length-from-client-options-2121
sentrivana Jun 28, 2023
5e88b42
Merge branch 'master' into max-string-length-from-client-options-2121
sentrivana Jun 29, 2023
bb267e4
Merge branch 'master' into max-string-length-from-client-options-2121
puittenbroek Jul 4, 2023
ce83a0c
test exceptiongroup test
Jul 4, 2023
248de7b
fix api docs circulr import
Jul 5, 2023
88a563d
Merge branch 'master' into max-string-length-from-client-options-2121
antonpirker Jul 11, 2023
e152e09
Merge branch 'master' into max-string-length-from-client-options-2121
antonpirker Jul 12, 2023
9482d9d
Merge branch 'master' into max-string-length-from-client-options-2121
antonpirker Jul 12, 2023
edaf59c
Some nitpicking
antonpirker Jul 19, 2023
18e605b
Merge branch 'master' into pr/puittenbroek/2171
antonpirker Jul 19, 2023
30ba9c7
Default value
antonpirker Jul 19, 2023
1e41716
Removed test for lineno, because it is too much hassle to keep up to …
antonpirker Jul 19, 2023
db2bda9
Renamed max_string_length to max_value_length to be inline with PHP o…
antonpirker Jul 19, 2023
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
12 changes: 10 additions & 2 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from sentry_sdk.tracing import trace, has_tracing_enabled
from sentry_sdk.transport import make_transport
from sentry_sdk.consts import (
DEFAULT_MAX_VALUE_LENGTH,
DEFAULT_OPTIONS,
INSTRUMENTER,
VERSION,
Expand Down Expand Up @@ -304,7 +305,12 @@ def _prepare_event(
"values": [
{
"stacktrace": current_stacktrace(
self.options["include_local_variables"]
include_local_variables=self.options.get(
"include_local_variables", True
),
max_value_length=self.options.get(
"max_value_length", DEFAULT_MAX_VALUE_LENGTH
),
),
"crashed": False,
"current": True,
Expand Down Expand Up @@ -339,7 +345,9 @@ def _prepare_event(
# generally not surface in before_send
if event is not None:
event = serialize(
event, max_request_body_size=self.options.get("max_request_body_size")
event,
max_request_body_size=self.options.get("max_request_body_size"),
max_value_length=self.options.get("max_value_length"),
)

before_send = self.options["before_send"]
Expand Down
5 changes: 4 additions & 1 deletion sentry_sdk/consts.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from sentry_sdk._types import TYPE_CHECKING

# up top to prevent circular import due to integration import
DEFAULT_MAX_VALUE_LENGTH = 1024

if TYPE_CHECKING:
import sentry_sdk

Expand Down Expand Up @@ -43,7 +46,6 @@

DEFAULT_QUEUE_SIZE = 100
DEFAULT_MAX_BREADCRUMBS = 100

MATCH_ALL = r".*"

FALSE_VALUES = [
Expand Down Expand Up @@ -206,6 +208,7 @@ def __init__(
], # type: Optional[Sequence[str]]
functions_to_trace=[], # type: Sequence[Dict[str, str]] # noqa: B006
event_scrubber=None, # type: Optional[sentry_sdk.scrubber.EventScrubber]
max_value_length=DEFAULT_MAX_VALUE_LENGTH, # type: int
):
# type: (...) -> None
pass
Expand Down
5 changes: 4 additions & 1 deletion sentry_sdk/integrations/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,10 @@ def _emit(self, record):
"values": [
{
"stacktrace": current_stacktrace(
client_options["include_local_variables"]
include_local_variables=client_options[
"include_local_variables"
],
max_value_length=client_options["max_value_length"],
),
"crashed": False,
"current": True,
Expand Down
7 changes: 5 additions & 2 deletions sentry_sdk/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def serialize(event, **kwargs):
keep_request_bodies = (
kwargs.pop("max_request_body_size", None) == "always"
) # type: bool
max_value_length = kwargs.pop("max_value_length", None) # type: Optional[int]

def _annotate(**meta):
# type: (**Any) -> None
Expand Down Expand Up @@ -295,7 +296,9 @@ def _serialize_node_impl(
if remaining_depth is not None and remaining_depth <= 0:
_annotate(rem=[["!limit", "x"]])
if is_databag:
return _flatten_annotated(strip_string(safe_repr(obj)))
return _flatten_annotated(
strip_string(safe_repr(obj), max_length=max_value_length)
)
return None

if is_databag and global_repr_processors:
Expand Down Expand Up @@ -396,7 +399,7 @@ def _serialize_node_impl(
if is_span_description:
return obj

return _flatten_annotated(strip_string(obj))
return _flatten_annotated(strip_string(obj, max_length=max_value_length))

#
# Start of serialize() function
Expand Down
46 changes: 30 additions & 16 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import sentry_sdk
from sentry_sdk._compat import PY2, PY33, PY37, implements_str, text_type, urlparse
from sentry_sdk._types import TYPE_CHECKING
from sentry_sdk.consts import DEFAULT_MAX_VALUE_LENGTH

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

MAX_STRING_LENGTH = 1024

BASE64_ALPHABET = re.compile(r"^[a-zA-Z0-9/+=]*$")

SENSITIVE_DATA_SUBSTITUTE = "[Filtered]"
Expand Down Expand Up @@ -468,6 +469,7 @@ def iter_stacks(tb):
def get_lines_from_file(
filename, # type: str
lineno, # type: int
max_length=None, # type: Optional[int]
loader=None, # type: Optional[Any]
module=None, # type: Optional[str]
):
Expand Down Expand Up @@ -496,11 +498,12 @@ def get_lines_from_file(

try:
pre_context = [
strip_string(line.strip("\r\n")) for line in source[lower_bound:lineno]
strip_string(line.strip("\r\n"), max_length=max_length)
for line in source[lower_bound:lineno]
]
context_line = strip_string(source[lineno].strip("\r\n"))
context_line = strip_string(source[lineno].strip("\r\n"), max_length=max_length)
post_context = [
strip_string(line.strip("\r\n"))
strip_string(line.strip("\r\n"), max_length=max_length)
for line in source[(lineno + 1) : upper_bound]
]
return pre_context, context_line, post_context
Expand All @@ -512,6 +515,7 @@ def get_lines_from_file(
def get_source_context(
frame, # type: FrameType
tb_lineno, # type: int
max_value_length=None, # type: Optional[int]
):
# type: (...) -> Tuple[List[Annotated[str]], Optional[Annotated[str]], List[Annotated[str]]]
try:
Expand All @@ -528,7 +532,9 @@ def get_source_context(
loader = None
lineno = tb_lineno - 1
if lineno is not None and abs_path:
return get_lines_from_file(abs_path, lineno, loader, module)
return get_lines_from_file(
abs_path, lineno, max_value_length, loader=loader, module=module
)
return [], None, []


Expand Down Expand Up @@ -602,9 +608,13 @@ def filename_for_module(module, abs_path):


def serialize_frame(
frame, tb_lineno=None, include_local_variables=True, include_source_context=True
frame,
tb_lineno=None,
include_local_variables=True,
include_source_context=True,
max_value_length=None,
):
# type: (FrameType, Optional[int], bool, bool) -> Dict[str, Any]
# type: (FrameType, Optional[int], bool, bool, Optional[int]) -> Dict[str, Any]
f_code = getattr(frame, "f_code", None)
if not f_code:
abs_path = None
Expand All @@ -630,7 +640,7 @@ def serialize_frame(

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

if include_local_variables:
Expand All @@ -639,8 +649,12 @@ def serialize_frame(
return rv


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

Expand All @@ -652,6 +666,7 @@ def current_stacktrace(include_local_variables=True, include_source_context=True
f,
include_local_variables=include_local_variables,
include_source_context=include_source_context,
max_value_length=max_value_length,
)
)
f = f.f_back
Expand Down Expand Up @@ -724,16 +739,19 @@ def single_exception_from_error_tuple(
if client_options is None:
include_local_variables = True
include_source_context = True
max_value_length = DEFAULT_MAX_VALUE_LENGTH # fallback
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"]

frames = [
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,
)
for tb in iter_stacks(tb)
]
Expand Down Expand Up @@ -819,9 +837,7 @@ def exceptions_from_error(
parent_id = exception_id
exception_id += 1

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

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

if max_length is None:
# This is intentionally not just the default such that one can patch `MAX_STRING_LENGTH` and affect `strip_string`.
max_length = MAX_STRING_LENGTH
max_length = DEFAULT_MAX_VALUE_LENGTH

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

Expand Down
8 changes: 4 additions & 4 deletions tests/integrations/sqlalchemy/test_sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
from sqlalchemy import text

from sentry_sdk import capture_message, start_transaction, configure_scope
from sentry_sdk.consts import SPANDATA
from sentry_sdk.consts import DEFAULT_MAX_VALUE_LENGTH, SPANDATA
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from sentry_sdk.serializer import MAX_EVENT_BYTES
from sentry_sdk.utils import json_dumps, MAX_STRING_LENGTH
from sentry_sdk.utils import json_dumps


def test_orm_queries(sentry_init, capture_events):
Expand Down Expand Up @@ -168,7 +168,7 @@ def test_large_event_not_truncated(sentry_init, capture_events):
)
events = capture_events()

long_str = "x" * (MAX_STRING_LENGTH + 10)
long_str = "x" * (DEFAULT_MAX_VALUE_LENGTH + 10)

with configure_scope() as scope:

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

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

# The _meta for other truncated fields should be there as well.
assert event["_meta"]["message"] == {
Expand Down
20 changes: 19 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from sentry_sdk.utils import HAS_CHAINED_EXCEPTIONS
from sentry_sdk.utils import logger
from sentry_sdk.serializer import MAX_DATABAG_BREADTH
from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS
from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, DEFAULT_MAX_VALUE_LENGTH

try:
from unittest import mock # python 3.3 and above
Expand Down Expand Up @@ -1118,3 +1118,21 @@ def test_multiple_positional_args(sentry_init):
with pytest.raises(TypeError) as exinfo:
sentry_init(1, None)
assert "Only single positional argument is expected" in str(exinfo.value)


@pytest.mark.parametrize(
"sdk_options, expected_data_length",
[
({}, DEFAULT_MAX_VALUE_LENGTH),
({"max_value_length": 1800}, 1800),
],
)
def test_max_value_length_option(
sentry_init, capture_events, sdk_options, expected_data_length
):
sentry_init(sdk_options)
events = capture_events()

capture_message("a" * 2000)

assert len(events[0]["message"]) == expected_data_length
6 changes: 5 additions & 1 deletion tests/test_exceptiongroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def test_exceptiongroup():
client_options={
"include_local_variables": True,
"include_source_context": True,
"max_value_length": 1024,
},
mechanism={"type": "test_suite", "handled": False},
)
Expand Down Expand Up @@ -162,6 +163,7 @@ def test_exceptiongroup_simple():
client_options={
"include_local_variables": True,
"include_source_context": True,
"max_value_length": 1024,
},
mechanism={"type": "test_suite", "handled": False},
)
Expand Down Expand Up @@ -190,7 +192,6 @@ def test_exceptiongroup_simple():
}
frame = exception_values[1]["stacktrace"]["frames"][0]
assert frame["module"] == "tests.test_exceptiongroup"
assert frame["lineno"] == 151
assert frame["context_line"] == " raise ExceptionGroup("


Expand All @@ -207,6 +208,7 @@ def test_exception_chain_cause():
client_options={
"include_local_variables": True,
"include_source_context": True,
"max_value_length": 1024,
},
mechanism={"type": "test_suite", "handled": False},
)
Expand Down Expand Up @@ -246,6 +248,7 @@ def test_exception_chain_context():
client_options={
"include_local_variables": True,
"include_source_context": True,
"max_value_length": 1024,
},
mechanism={"type": "test_suite", "handled": False},
)
Expand Down Expand Up @@ -284,6 +287,7 @@ def test_simple_exception():
client_options={
"include_local_variables": True,
"include_source_context": True,
"max_value_length": 1024,
},
mechanism={"type": "test_suite", "handled": False},
)
Expand Down
17 changes: 17 additions & 0 deletions tests/test_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,20 @@ def test_no_trimming_if_max_request_body_size_is_always(body_normalizer):
result = body_normalizer(data, max_request_body_size="always")

assert result == data


def test_max_value_length_default(body_normalizer):
data = {"key": "a" * 2000}

result = body_normalizer(data)

assert len(result["key"]) == 1024 # fallback max length


def test_max_value_length(body_normalizer):
data = {"key": "a" * 2000}

max_value_length = 1800
result = body_normalizer(data, max_value_length=max_value_length)

assert len(result["key"]) == max_value_length