blob: cd713439ea5db7b8b34302b37e8eace66e18b994 [file] [log] [blame]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002import inspect
3import traceback
4import bb.namedtuple_with_abc
5from collections import namedtuple
6
7
8class TracebackEntry(namedtuple.abc):
9 """Pickleable representation of a traceback entry"""
10 _fields = 'filename lineno function args code_context index'
11 _header = ' File "{0.filename}", line {0.lineno}, in {0.function}{0.args}'
12
13 def format(self, formatter=None):
14 if not self.code_context:
15 return self._header.format(self) + '\n'
16
17 formatted = [self._header.format(self) + ':\n']
18
19 for lineindex, line in enumerate(self.code_context):
20 if formatter:
21 line = formatter(line)
22
23 if lineindex == self.index:
24 formatted.append(' >%s' % line)
25 else:
26 formatted.append(' %s' % line)
27 return formatted
28
29 def __str__(self):
30 return ''.join(self.format())
31
32def _get_frame_args(frame):
33 """Get the formatted arguments and class (if available) for a frame"""
34 arginfo = inspect.getargvalues(frame)
35
36 try:
37 if not arginfo.args:
38 return '', None
39 # There have been reports from the field of python 2.6 which doesn't
40 # return a namedtuple here but simply a tuple so fallback gracefully if
41 # args isn't present.
42 except AttributeError:
43 return '', None
44
45 firstarg = arginfo.args[0]
46 if firstarg == 'self':
47 self = arginfo.locals['self']
48 cls = self.__class__.__name__
49
50 arginfo.args.pop(0)
51 del arginfo.locals['self']
52 else:
53 cls = None
54
55 formatted = inspect.formatargvalues(*arginfo)
56 return formatted, cls
57
58def extract_traceback(tb, context=1):
59 frames = inspect.getinnerframes(tb, context)
60 for frame, filename, lineno, function, code_context, index in frames:
61 formatted_args, cls = _get_frame_args(frame)
62 if cls:
63 function = '%s.%s' % (cls, function)
64 yield TracebackEntry(filename, lineno, function, formatted_args,
65 code_context, index)
66
67def format_extracted(extracted, formatter=None, limit=None):
68 if limit:
69 extracted = extracted[-limit:]
70
71 formatted = []
72 for tracebackinfo in extracted:
73 formatted.extend(tracebackinfo.format(formatter))
74 return formatted
75
76
77def format_exception(etype, value, tb, context=1, limit=None, formatter=None):
78 formatted = ['Traceback (most recent call last):\n']
79
80 if hasattr(tb, 'tb_next'):
81 tb = extract_traceback(tb, context)
82
83 formatted.extend(format_extracted(tb, formatter, limit))
84 formatted.extend(traceback.format_exception_only(etype, value))
85 return formatted
86
87def to_string(exc):
88 if isinstance(exc, SystemExit):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060089 if not isinstance(exc.code, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050090 return 'Exited with "%d"' % exc.code
91 return str(exc)