From 2a977f67055ad10b176322846549c70469d18f61 Mon Sep 17 00:00:00 2001 From: Keerthi Vasan Date: Fri, 2 Feb 2024 16:15:12 +0530 Subject: [PATCH] completely switch to repr, added tests for repr exception reports --- django/views/debug.py | 25 +++++---- tests/exception_report/__init__.py | 0 .../exception_report/test_large_exception.py | 51 +++++++++++++++++++ 3 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 tests/exception_report/__init__.py create mode 100644 tests/exception_report/test_large_exception.py diff --git a/django/views/debug.py b/django/views/debug.py index 9d49f3a0c9..696fcf04db 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -6,12 +6,12 @@ import reprlib import sys import types import warnings +from collections.abc import Sized from pathlib import Path from django.conf import settings from django.http import Http404, HttpResponse, HttpResponseNotFound from django.template import Context, Engine, TemplateDoesNotExist -from django.template.defaultfilters import pprint from django.urls import resolve from django.utils import timezone from django.utils.datastructures import MultiValueDict @@ -311,8 +311,16 @@ class SafeExceptionReporterFilter: class ExceptionReporter: """Organize and coordinate reporting on exceptions.""" - repr_instance = reprlib.Repr(maxstring=4096, maxlist=1000) - MAX_VAR_SIZE_PRETTY_PRINT = 512 * 1024 # 512KB + repr_instance = reprlib.Repr() + repr_instance.indent = 2 + repr_instance.maxdeque = 4096 + repr_instance.maxstring = 4096 + repr_instance.maxlist = 4096 + repr_instance.maxset = 4096 + repr_instance.maxdict = 4096 + repr_instance.maxfrozenset = 4096 + repr_instance.maxarray = 4096 + repr_instance.maxother = 4096 @property def html_template_path(self): @@ -352,18 +360,13 @@ class ExceptionReporter: self.postmortem = self.exc_value.chain or [self.exc_value] frames = self.get_traceback_frames() - for i, frame in enumerate(frames): if "vars" in frame: frame_vars = [] for k, v in frame["vars"]: - if sys.getsizeof(v) > self.MAX_VAR_SIZE_PRETTY_PRINT: - v = self.repr_instance.repr(v) - else: - v = pprint(v) - # Trim large blobs of data - if len(v) > 4096: - v = "%s… " % (v[0:4096], len(v)) + if isinstance(v, Sized) and len(v) > 4096: + self.repr_instance.fillvalue = "...%d more" % (len(v) - 4096) + v = self.repr_instance.repr(v) frame_vars.append((k, v)) frame["vars"] = frame_vars frames[i] = frame diff --git a/tests/exception_report/__init__.py b/tests/exception_report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/exception_report/test_large_exception.py b/tests/exception_report/test_large_exception.py new file mode 100644 index 0000000000..4c4204e347 --- /dev/null +++ b/tests/exception_report/test_large_exception.py @@ -0,0 +1,51 @@ +import sys + +from django.test import TestCase +from django.test.client import RequestFactory +from django.views.debug import ExceptionReporter + + +class ExceptionReport(TestCase): + factory = RequestFactory() + + def test_large_sizable_object(self): + lg = list(range(50 * 1024 * 1024)) + try: + request = self.factory.get("/") + lg["a"] + except TypeError: + exc_type, exc_value, tb = sys.exc_info() + + reporter = ExceptionReporter(request, exc_type, exc_value, tb) + d = reporter.get_traceback_data() + vars = d["lastframe"]["vars"] + + for k, v in vars: + if k == "lg": + i = v.index("...") + # Check if it has been trimmed + self.assertGreater(i, -1) + + # Construct list with elements before trimming + ls = eval(v[:i] + "]") + + # Check if length of trimmed list is our limit + self.assertEqual(len(ls), 4096) + break + + def test_non_sizable_object(self): + num = 10000000 + try: + request = self.factory.get("/") + num["a"] + except TypeError: + exc_type, exc_value, tb = sys.exc_info() + + reporter = ExceptionReporter(request, exc_type, exc_value, tb) + d = reporter.get_traceback_data() + vars = d["lastframe"]["vars"] + + for k, v in vars: + if k == "a": + self.assertEqual(v, str(num)) + break