From 4df730808958677562070bc593323e5069aba62b Mon Sep 17 00:00:00 2001 From: midichef <67946319+midichef@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:40:17 -0800 Subject: [PATCH 1/3] [errors-] fix: make traces include most callers of stacktrace() Callers like getCell() were being wrongly excluded. Also fixes IndexError when trimming short call stacks, three levels deep. --- visidata/errors.py | 54 +++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/visidata/errors.py b/visidata/errors.py index af48d2167..28730ae3c 100644 --- a/visidata/errors.py +++ b/visidata/errors.py @@ -1,4 +1,5 @@ import traceback +import sys import re from visidata import vd, VisiData @@ -11,25 +12,43 @@ 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 @@ -37,7 +56,8 @@ 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: From aa4285bfa1db110fdf88073d83dba9a4fda3af4a Mon Sep 17 00:00:00 2001 From: midichef <67946319+midichef@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:37:58 -0800 Subject: [PATCH 2/3] [main-] format vd_cli exceptions to be caretless --- visidata/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/visidata/main.py b/visidata/main.py index 19e003321..1bc32ec29 100755 --- a/visidata/main.py +++ b/visidata/main.py @@ -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 @@ -392,6 +392,9 @@ def vd_cli(): print(e) if options.debug: raise + except Exception as e: + for l in stacktrace(): #show the stack trace without carets + print(l) sys.stderr.flush() sys.stdout.flush() From 9dfbb27afaca03e855fd6c0335b9e4f1bb2ffe8e Mon Sep 17 00:00:00 2001 From: Saul Pwanson Date: Sat, 11 Jan 2025 17:22:38 -0800 Subject: [PATCH 3/3] Use stderr for cli stacktraces --- visidata/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/visidata/main.py b/visidata/main.py index 1bc32ec29..0f242e26d 100755 --- a/visidata/main.py +++ b/visidata/main.py @@ -389,12 +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) + print(l, file=sys.stderr) sys.stderr.flush() sys.stdout.flush()