Stacktrace ordering: stack-of-plates as an option

implemented in-python; doing this using flex quickly became a mess
This commit is contained in:
Klaas van Schelven
2024-04-04 10:40:32 +02:00
parent 2854d6f3ee
commit 09a716a8d7
4 changed files with 60 additions and 14 deletions

View File

@@ -42,31 +42,41 @@
</div>
<div class="ml-auto pr-4">{# indicator for frame's position in stacktrace #}
{% if forloop.last %}
{% if forloop.parentloop.last %}
{% if stack_of_plates and forloop.first or not stack_of_plates and forloop.last %}
{% if stack_of_plates and forloop.parentloop.first or not stack_of_plates and forloop.parentloop.last %}
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">raise {{ exception.type }}</span>
{% else %}
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">raise (handled)</span>
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">raise {{ exception.type }} (handled)</span>
{% endif %}
{% elif forloop.first %} {# strictly speaking, not actually "else", but to avoid clutter we hide 'outermost' info when this is also the raise-point #}
{% if forloop.parentloop.last %}
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">outermost</span>
{% elif stack_of_plates and forloop.last or not stack_of_plates and forloop.first %} {# strictly speaking, not actually "else", but to avoid clutter we hide 'outermost' info when this is also the raise-point #}
{% if stack_of_plates and forloop.parentloop.first or not stack_of_plates and forloop.parentloop.last %}
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">begin</span>
{% else %}
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">outermost (handled)</span>
{% comment %}I find it (quite too) hard to come up with a good name for this type of frame that is both short and clear. Thoughts so fare were:
* try...
* start try
* start failing try (handled)
* "begin handled" ()
* "begin handled" {{ exception.type }}
* "outermost handled"
* "divergence w/ main exception"
* first unique frame
{% endcomment %}
<span class="bg-slate-200 pl-2 pr-2 pt-1 pb-1 rounded-md whitespace-nowrap">try…</span>
{% endif %}
{% endif %}
</div>
<div class="pr-4">{# chevron #}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5 js-chevron transition-all {% if forloop.parentloop.last and forloop.last %}rotate-180{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5 js-chevron transition-all {% if frame.raise_point %}rotate-180{% endif %}">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</div>
</div> {# per frame header div #}
<div class="js-frame-details {% if frame.in_app %}js-in-app{% endif %} border-slate-400 overflow-hidden transition-all {% if forloop.parentloop.last and forloop.last %}js-raising-frame{% endif %}"
{% if not forloop.parentloop.last or not forloop.last %}data-collapsed="true" style="height: 0px"{% endif %}> {# collapsable part #}
<div class="js-frame-details {% if frame.in_app %}js-in-app{% endif %} border-slate-400 overflow-hidden transition-all {% if stack_of_plates and forloop.parentloop.first and forloop.first or not stack_of_plates and forloop.parentloop.last and forloop.last %}js-raising-frame{% endif %}"
{% if not frame.raise_point %}data-collapsed="true" style="height: 0px"{% endif %}> {# collapsable part #}
<div class="pl-6 pr-6 {% if not forloop.last %}border-b-2 border-slate-400{% endif %}">{# convience div for padding & border; the border is basically the top-border of the next header #}
{% if "context_line" in frame and frame.context_line is not None %}
@@ -114,9 +124,13 @@
{# </div> #} {# per-exception div in the multi-exception case #}
{% if not forloop.last %}
<div class="italic pt-4">During handling of the above exception another exception occurred or was intentionally reraised:</div>
{# note: the above is specific to Python. We cannot distinguish between Python's 2 types of chained exceptions because the info is not sent by the client #}
{# we could try to infer this from the stacktrace, but parsing potentially arbitrarily formatted partial code is brittle #}
{% if not stack_of_plates %}
<div class="italic pt-4">During handling of the above exception another exception occurred or was intentionally reraised:</div>
{# note: the above is specific to Python. We cannot distinguish between Python's 2 types of chained exceptions because the info is not sent by the client #}
{# we could try to infer this from the stacktrace, but parsing potentially arbitrarily formatted partial code is brittle #}
{% else %}
<div class="italic pt-4">The above exception was caused by (occurred or was intentially reraised during the handling of) the following exception:</div>
{% endif %}
{% endif %}
{% endfor %}

View File

@@ -1,3 +1,4 @@
import uuid
import json
import time
from io import StringIO
@@ -485,6 +486,8 @@ class IntegrationTest(DjangoTestCase):
with open(filename) as f:
data = json.loads(f.read())
data["event_id"] = uuid.uuid4().hex
if "timestamp" not in data:
# as per send_json command ("weirdly enough a large numer of sentry test data don't actually...")
data["timestamp"] = time.time()

View File

@@ -177,6 +177,25 @@ def issue_event_stacktrace(request, issue, event_pk):
# the list of exceptions, but we don't aim for endless backwards compat (yet) so we don't.
exceptions = parsed_data["exception"]["values"] if "exception" in parsed_data else None
# NOTE: I considered making this a clickable button of some sort, but decided against it in the end. Getting the UI
# right is quite hard (https://ux.stackexchange.com/questions/1318) but more generally I would assume that having
# your whole screen turned upside down is not something you do willy-nilly. Better to just have good defaults and
# (possibly later) have this as something that is configurable at the user level.
stack_of_plates = event.platform != "python" # Python is the only platform that has chronological stacktraces
if exceptions is not None and len(exceptions) > 0:
if exceptions[-1].get('stacktrace') and exceptions[-1]['stacktrace'].get('frames'):
exceptions[-1]['stacktrace']['frames'][-1]['raise_point'] = True
if stack_of_plates:
# NOTE manipulation of parsed_data going on here, this could be a trap if other parts depend on it
# (e.g. grouper)
exceptions = [e for e in reversed(exceptions)]
for exception in exceptions:
if not exception.get('stacktrace'):
continue
exception['stacktrace']['frames'] = [f for f in reversed(exception['stacktrace']['frames'])]
if "logentry" in parsed_data:
logentry = parsed_data["logentry"]
if "formatted" not in logentry:
@@ -195,7 +214,7 @@ def issue_event_stacktrace(request, issue, event_pk):
"is_event_page": True,
"parsed_data": parsed_data,
"exceptions": exceptions,
"issue_grouper": get_issue_grouper_for_data(parsed_data),
"stack_of_plates": stack_of_plates,
"mute_options": GLOBAL_MUTE_OPTIONS,
})
@@ -262,6 +281,7 @@ def issue_grouping(request, issue):
return _handle_post(request, issue)
last_event = issue.event_set.order_by("timestamp").last() # the template needs this for the tabs, we pick the last
parsed_data = json.loads(last_event.data) # should this not just be saved on the Issue??
return render(request, "issues/issue_grouping.html", {
"tab": "grouping",
"project": issue.project,
@@ -269,6 +289,7 @@ def issue_grouping(request, issue):
"event": last_event,
"is_event_page": False,
"parsed_data": json.loads(last_event.data),
"issue_grouper": get_issue_grouper_for_data(parsed_data),
"mute_options": GLOBAL_MUTE_OPTIONS,
})

View File

@@ -1028,10 +1028,18 @@ select {
appearance: none;
}
.flex-row-reverse {
flex-direction: row-reverse;
}
.flex-col {
flex-direction: column;
}
.flex-col-reverse {
flex-direction: column-reverse;
}
.place-content-end {
place-content: end;
}