Skip to content
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

stacktrace: fix missing caller frames and remove carets from vd_cli() traces #2651

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
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
54 changes: 37 additions & 17 deletions visidata/errors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import traceback
import sys
import re

from visidata import vd, VisiData
Expand All @@ -11,33 +12,52 @@ class ExpectedException(Exception):
pass


def stacktrace(e=None):
'''Return a list of strings. Includes extra callstack levels above the
level where the exception was technically caught, to aid debugging.'''
def stacktrace(e=None, exclude_caller=False):
'''Return a list of strings for the stack trace, without newlines
at the end. If an exception handler is executing, and *e* is none,
the stack trace includes extra levels of callers beyond the level
where the exception was caught. If *exclude_caller* is True, the
trace will exclude the function that called stacktrace(). The
trace will exclude several uninformative levels that are run
in interactive visidata.'''

if not e:
stack = ''.join(traceback.format_stack()).strip().splitlines()
trim_levels = 3 # calling function -> stacktrace() -> format_stack()
if e:
return traceback.format_exception_only(type(e), e)
#in Python 3.11 we can replace sys.exc_info() with sys.exception()
handling = (sys.exc_info() != (None, None, None))

stack = ''.join(traceback.format_stack()).strip().splitlines()

if handling:
trim_levels = 2 # remove levels for stacktrace() -> format_stack()
if exclude_caller:
trim_levels += 1
trace_above = stack[:-2*trim_levels]
else:
trace_above = stack
if trace_above:
trace_above[0] = ' ' + trace_above[0] #fix indent level of first line
try:
# remove several levels of uninformative stacktrace in typical interactive vd
idx = trace_above.index(' ret = vd.mainloop(scr)')
trace_above = trace_above[idx+1:]
except ValueError:
pass
# remove lines that mark error columns with carets and sometimes tildes
trace_below = [ line for line in traceback.format_exc().strip().splitlines() if not re.match('^ *~*\\^+$', line) ]
return [trace_below[0]] + trace_above + trace_below[1:]
return traceback.format_exception_only(type(e), e)
try:
# remove several levels of uninformative stacktrace in typical interactive vd
idx = trace_above.index(' ret = vd.mainloop(scr)')
trace_above = trace_above[idx+1:]
except ValueError:
pass
if not handling:
return trace_above
# remove lines that mark error columns with carets and sometimes tildes
trace_below = [ line for line in traceback.format_exc().strip().splitlines() if not re.match('^ *~*\\^+$', line) ]
# move the "Traceback (most recent call last) header to the top of the output
return [trace_below[0]] + trace_above + trace_below[1:]


@VisiData.api
def exceptionCaught(vd, exc=None, status=True, **kwargs):
'Add *exc* to list of last errors and add to status history. Show on left status bar if *status* is True. Reraise exception if options.debug is True.'
if isinstance(exc, ExpectedException): # already reported, don't log
return
vd.lastErrors.append(stacktrace())
# save a stack trace that does not include this function
vd.lastErrors.append(stacktrace(exclude_caller=True))
if status:
vd.status(f'{type(exc).__name__}: {exc}', priority=2)
else:
Expand Down
7 changes: 5 additions & 2 deletions visidata/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import warnings
import builtins # to override print

from visidata import vd, options, run, BaseSheet, AttrDict
from visidata import vd, options, run, BaseSheet, AttrDict, stacktrace
from visidata import Path
from visidata.settings import _get_config_file
import visidata
Expand Down Expand Up @@ -389,9 +389,12 @@ def vd_cli():
if vd.options.debug:
raise
except FileNotFoundError as e:
print(e)
print(e, file=sys.stderr)
if options.debug:
raise
except Exception as e:
for l in stacktrace(): #show the stack trace without carets
print(l, file=sys.stderr)

sys.stderr.flush()
sys.stdout.flush()
Expand Down
Loading