├── .gitignore ├── README.md ├── setup.cfg ├── setup.py ├── test └── test_tracebackturbo.py └── tracebackturbo ├── __init__.py └── tracebackturbo.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .idea 3 | build 4 | dist 5 | tracebackturbo.egg-info 6 | *.pyc 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tracebackturbo 2 | ============== 3 | A drop-in replacement for the python2 [traceback module](http://docs.python.org/library/traceback.html) 4 | that dumps the local variable scope aside normal stack traces. 5 | 6 | Usage 7 | ----- 8 | ```python 9 | import tracebackturbo as traceback 10 | 11 | def erroneous_function(): 12 | ham = u"unicode string with umlauts äöü." 13 | eggs = "binary string with umlauts äöü." 14 | i = 23 15 | if i>5: 16 | raise Exception("it's a trap!") 17 | 18 | try: 19 | erroneous_function() 20 | except: 21 | print traceback.format_exc(with_vars=True) 22 | ``` 23 | 24 | Sample Output 25 | ------------- 26 | This is the output of tracebackturbo: 27 | ``` 28 | Traceback Turbo (most recent call last): 29 | File "test.py", line 11, in 30 | Local variables: 31 | __builtins__ = 32 | __doc__ = None 33 | __file__ = "x" 34 | __name__ = "__main__" 35 | __package__ = None 36 | erroneous_function = 37 | traceback = > open(testfile, 'w'), """ 78 | def test(): 79 | raise ValueError""" 80 | 81 | if 'test_bug737473' in sys.modules: 82 | del sys.modules['test_bug737473'] 83 | import test_bug737473 84 | 85 | try: 86 | test_bug737473.test() 87 | except ValueError: 88 | # this loads source code to linecache 89 | traceback.extract_tb(sys.exc_traceback) 90 | 91 | # If this test runs too quickly, test_bug737473.py's mtime 92 | # attribute will remain unchanged even if the file is rewritten. 93 | # Consequently, the file would not reload. So, added a sleep() 94 | # delay to assure that a new, distinct timestamp is written. 95 | # Since WinME with FAT32 has multisecond resolution, more than 96 | # three seconds are needed for this test to pass reliably :-( 97 | time.sleep(4) 98 | 99 | print >> open(testfile, 'w'), """ 100 | def test(): 101 | raise NotImplementedError""" 102 | reload(test_bug737473) 103 | try: 104 | test_bug737473.test() 105 | except NotImplementedError: 106 | src = traceback.extract_tb(sys.exc_traceback)[-1][-2] 107 | self.assertEqual(src, 'raise NotImplementedError') 108 | finally: 109 | sys.path[:] = savedpath 110 | for f in os.listdir(testdir): 111 | os.unlink(os.path.join(testdir, f)) 112 | os.rmdir(testdir) 113 | 114 | def test_base_exception(self): 115 | # Test that exceptions derived from BaseException are formatted right 116 | e = KeyboardInterrupt() 117 | lst = traceback.format_exception_only(e.__class__, e) 118 | self.assertEqual(lst, ['KeyboardInterrupt\n']) 119 | 120 | # String exceptions are deprecated, but legal. The quirky form with 121 | # separate "type" and "value" tends to break things, because 122 | # not isinstance(value, type) 123 | # and a string cannot be the first argument to issubclass. 124 | # 125 | # Note that sys.last_type and sys.last_value do not get set if an 126 | # exception is caught, so we sort of cheat and just emulate them. 127 | # 128 | # test_string_exception1 is equivalent to 129 | # 130 | # >>> raise "String Exception" 131 | # 132 | # test_string_exception2 is equivalent to 133 | # 134 | # >>> raise "String Exception", "String Value" 135 | # 136 | def test_string_exception1(self): 137 | str_type = "String Exception" 138 | err = traceback.format_exception_only(str_type, None) 139 | self.assertEqual(len(err), 1) 140 | self.assertEqual(err[0], str_type + '\n') 141 | 142 | def test_string_exception2(self): 143 | str_type = "String Exception" 144 | str_value = "String Value" 145 | err = traceback.format_exception_only(str_type, str_value) 146 | self.assertEqual(len(err), 1) 147 | self.assertEqual(err[0], str_type + ': ' + str_value + '\n') 148 | 149 | def test_format_exception_only_bad__str__(self): 150 | class X(Exception): 151 | def __str__(self): 152 | 1/0 153 | err = traceback.format_exception_only(X, X()) 154 | self.assertEqual(len(err), 1) 155 | str_value = '' % X.__name__ 156 | self.assertEqual(err[0], X.__name__ + ': ' + str_value + '\n') 157 | 158 | def test_without_exception(self): 159 | err = traceback.format_exception_only(None, None) 160 | self.assertEqual(err, ['None\n']) 161 | 162 | def test_format_vars(self): 163 | err = traceback.format_vars([ 164 | ('bstr', 'binary string'), 165 | ('ustr', u'unicode string'), 166 | ('int', 5), 167 | ('float', 5.23), 168 | ('function', lambda x: x), 169 | ]) 170 | bstr, ustr, int, float, function = err.splitlines() 171 | self.assertTrue(bstr.startswith(' bstr = "binary string"')) 172 | self.assertTrue(ustr.startswith(' ustr = u"unicode string"')) 173 | self.assertTrue(int.startswith(' int = 5')) 174 | self.assertTrue(float.startswith(' float = 5.23')) 175 | self.assertTrue(function.startswith(' function = at ')) 176 | self.assertEqual(type(err), type("")) 177 | 178 | 179 | class TracebackFormatTests(unittest.TestCase): 180 | 181 | def test_traceback_format(self): 182 | try: 183 | raise KeyError('blah') 184 | except KeyError: 185 | type_, value, tb = sys.exc_info() 186 | traceback_fmt = 'Traceback (most recent call last):\n' + \ 187 | ''.join(traceback.format_tb(tb, with_vars=False)) 188 | file_ = StringIO() 189 | traceback_print(tb, file_) 190 | python_fmt = file_.getvalue() 191 | else: 192 | raise Error("unable to create test traceback string") 193 | 194 | # Make sure that Python and the traceback module format the same thing 195 | self.assertEquals(traceback_fmt, python_fmt) 196 | 197 | # Make sure that the traceback is properly indented. 198 | tb_lines = python_fmt.splitlines() 199 | self.assertEquals(len(tb_lines), 3) 200 | banner, location, source_line = tb_lines 201 | self.assertTrue(banner.startswith('Traceback')) 202 | self.assertTrue(location.startswith(' File')) 203 | self.assertTrue(source_line.startswith(' raise')) 204 | 205 | def test_traceback_format_with_vars(self): 206 | try: 207 | raise KeyError('blah') 208 | except KeyError: 209 | type_, value, tb = sys.exc_info() 210 | traceback_fmt = 'Traceback (most recent call last):\n' + \ 211 | ''.join(traceback.format_tb(tb, with_vars=True)) 212 | else: 213 | raise Error("unable to create test traceback string") 214 | 215 | # Make sure that the traceback is properly indented. 216 | tb_lines = traceback_fmt.splitlines() 217 | self.assertEquals(len(tb_lines), 8) 218 | banner, location, variables, var1, var2, var3, var4, source_line = tb_lines 219 | self.assertTrue(banner.startswith('Traceback')) 220 | self.assertTrue(location.startswith(' File')) 221 | self.assertTrue(variables.startswith(' Local variables')) 222 | self.assertTrue(source_line.startswith(' raise')) 223 | 224 | 225 | def test_main(): 226 | run_unittest(TracebackCases, TracebackFormatTests) 227 | 228 | 229 | if __name__ == "__main__": 230 | test_main() 231 | -------------------------------------------------------------------------------- /tracebackturbo/__init__.py: -------------------------------------------------------------------------------- 1 | from .tracebackturbo import * 2 | -------------------------------------------------------------------------------- /tracebackturbo/tracebackturbo.py: -------------------------------------------------------------------------------- 1 | """Extract, format and print information about Python stack traces.""" 2 | # 2014-06-10 benjamin: rebased to python 2.7.7 3 | # 2010-01-27, benjamin: 4 | # this is a patched traceback.py module (hg id 1728133edce0) 5 | # in addition to the original traceback module, this moduls dumps 6 | # the scope (local and global variables) of all traced functions. 7 | # note: this module holds lots of legacy code and duplicates, 8 | # we could clean-up things a bit for ease of maintenance. 9 | 10 | import linecache 11 | import sys 12 | import types 13 | 14 | __all__ = ['extract_stack', 'extract_tb', 'format_exception', 15 | 'format_exception_only', 'format_list', 'format_stack', 16 | 'format_tb', 'print_exc', 'format_exc', 'print_exception', 17 | 'print_last', 'print_stack', 'print_tb', 'tb_lineno', 18 | 'print_vars', 'format_vars'] 19 | 20 | def _print(file, str='', terminator='\n'): 21 | file.write(str+terminator) 22 | 23 | def print_vars(vars, width=72, file=None): 24 | """Print a list of variables as given by globals.items()""" 25 | 26 | if file is None: 27 | file = sys.stderr 28 | for key, value in vars: 29 | var = ' ' 30 | if isinstance(value, str): 31 | value = '"' + value.encode("string_escape") + '"' 32 | if isinstance(value, unicode): 33 | value = 'u"' + value.encode(sys.getdefaultencoding(), 'replace').encode("string_escape") + '"' 34 | try: 35 | var += "%s = %s" % (key, value) 36 | except Exception, e: 37 | var += "%s = %s" % (key, type(value)) 38 | if len(var) > width: 39 | var = var[: width - 3] + '...' 40 | _print(file, var) 41 | 42 | 43 | def format_vars(vars, width=72): 44 | """Format a list of variables, arguments are like print_vars""" 45 | 46 | result = "" 47 | for key, value in vars: 48 | var = ' ' # fixed indentation 49 | if isinstance(value, str): 50 | value = '"' + value.encode("string_escape") + '"' 51 | if isinstance(value, unicode): 52 | value = 'u"' + value.encode(sys.getdefaultencoding(), 'replace').encode("string_escape") + '"' 53 | try: 54 | var += "%s = %s" % (key, value) 55 | except: 56 | var += "%s = %s" % (key, type(value)) 57 | if len(var) > width: 58 | var = var[: width - 3] + '...' 59 | result += var + "\n" 60 | return result 61 | 62 | def print_list(extracted_list, file=None, with_vars=True): 63 | """Print the list of tuples as returned by extract_tb() or 64 | extract_stack() as a formatted stack trace to the given file.""" 65 | if file is None: 66 | file = sys.stderr 67 | for filename, lineno, name, line, locals in extracted_list: 68 | _print(file, 69 | ' File "%s", line %d, in %s' % (filename,lineno,name)) 70 | if with_vars: 71 | _print(file, ' Local variables:') 72 | print_vars(sorted(locals), file=file) 73 | if line: 74 | _print(file, ' %s' % line.strip()) 75 | 76 | def format_list(extracted_list, with_vars=True): 77 | """Format a list of traceback entry tuples for printing. 78 | 79 | Given a list of tuples as returned by extract_tb() or 80 | extract_stack(), return a list of strings ready for printing. 81 | Each string in the resulting list corresponds to the item with the 82 | same index in the argument list. Each string ends in a newline; 83 | the strings may contain internal newlines as well, for those items 84 | whose source text line is not None. 85 | """ 86 | list = [] 87 | for filename, lineno, name, line, locals in extracted_list: 88 | item = ' File "%s", line %d, in %s\n' % (filename,lineno,name) 89 | if with_vars: 90 | item += ' Local variables:\n' 91 | item += format_vars(sorted(locals)) 92 | if line: 93 | item = item + ' %s\n' % line.strip() 94 | list.append(item) 95 | return list 96 | 97 | 98 | def print_tb(tb, limit=None, file=None, with_vars=True): 99 | """Print up to 'limit' stack trace entries from the traceback 'tb'. 100 | 101 | If 'limit' is omitted or None, all entries are printed. If 'file' 102 | is omitted or None, the output goes to sys.stderr; otherwise 103 | 'file' should be an open file or file-like object with a write() 104 | method. 105 | """ 106 | if file is None: 107 | file = sys.stderr 108 | if limit is None: 109 | if hasattr(sys, 'tracebacklimit'): 110 | limit = sys.tracebacklimit 111 | n = 0 112 | while tb is not None and (limit is None or n < limit): 113 | f = tb.tb_frame 114 | lineno = tb.tb_lineno 115 | co = f.f_code 116 | filename = co.co_filename 117 | name = co.co_name 118 | _print(file, 119 | ' File "%s", line %d, in %s' % (filename, lineno, name)) 120 | linecache.checkcache(filename) 121 | line = linecache.getline(filename, lineno, f.f_globals) 122 | locals = f.f_locals.items() 123 | if with_vars: 124 | _print(file, ' Local variables:') 125 | print_vars(sorted(locals), file=file) 126 | if line: _print(file, ' ' + line.strip()) 127 | tb = tb.tb_next 128 | n = n+1 129 | 130 | def format_tb(tb, limit = None, with_vars=True): 131 | """A shorthand for 'format_list(extract_tb(tb, limit))'.""" 132 | return format_list(extract_tb(tb, limit), with_vars) 133 | 134 | def extract_tb(tb, limit = None): 135 | """Return list of up to limit pre-processed entries from traceback. 136 | 137 | This is useful for alternate formatting of stack traces. If 138 | 'limit' is omitted or None, all entries are extracted. A 139 | pre-processed stack trace entry is a quadruple (filename, line 140 | number, function name, text) representing the information that is 141 | usually printed for a stack trace. The text is a string with 142 | leading and trailing whitespace stripped; if the source is not 143 | available it is None. 144 | """ 145 | if limit is None: 146 | if hasattr(sys, 'tracebacklimit'): 147 | limit = sys.tracebacklimit 148 | list = [] 149 | n = 0 150 | while tb is not None and (limit is None or n < limit): 151 | f = tb.tb_frame 152 | lineno = tb.tb_lineno 153 | co = f.f_code 154 | filename = co.co_filename 155 | name = co.co_name 156 | linecache.checkcache(filename) 157 | line = linecache.getline(filename, lineno, f.f_globals) 158 | if line: line = line.strip() 159 | else: line = None 160 | locals = f.f_locals.items() 161 | list.append((filename, lineno, name, line, locals)) 162 | tb = tb.tb_next 163 | n = n+1 164 | return list 165 | 166 | 167 | def print_exception(etype, value, tb, limit=None, file=None, with_vars=True): 168 | """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. 169 | 170 | This differs from print_tb() in the following ways: (1) if 171 | traceback is not None, it prints a header "Traceback (most recent 172 | call last):"; (2) it prints the exception type and value after the 173 | stack trace; (3) if type is SyntaxError and value has the 174 | appropriate format, it prints the line where the syntax error 175 | occurred with a caret on the next line indicating the approximate 176 | position of the error. 177 | """ 178 | if file is None: 179 | file = sys.stderr 180 | if tb: 181 | _print(file, 'Traceback Turbo (most recent call last):') 182 | print_tb(tb, limit, file, with_vars) 183 | lines = format_exception_only(etype, value) 184 | for line in lines: 185 | _print(file, line, '') 186 | 187 | def format_exception(etype, value, tb, limit = None, with_vars = True): 188 | """Format a stack trace and the exception information. 189 | 190 | The arguments have the same meaning as the corresponding arguments 191 | to print_exception(). The return value is a list of strings, each 192 | ending in a newline and some containing internal newlines. When 193 | these lines are concatenated and printed, exactly the same text is 194 | printed as does print_exception(). 195 | """ 196 | if tb: 197 | list = ['Traceback Turbo (most recent call last):\n'] 198 | list = list + format_tb(tb, limit, with_vars) 199 | else: 200 | list = [] 201 | list = list + format_exception_only(etype, value) 202 | return list 203 | 204 | def format_exception_only(etype, value): 205 | """Format the exception part of a traceback. 206 | 207 | The arguments are the exception type and value such as given by 208 | sys.last_type and sys.last_value. The return value is a list of 209 | strings, each ending in a newline. 210 | 211 | Normally, the list contains a single string; however, for 212 | SyntaxError exceptions, it contains several lines that (when 213 | printed) display detailed information about where the syntax 214 | error occurred. 215 | 216 | The message indicating which exception occurred is always the last 217 | string in the list. 218 | 219 | """ 220 | 221 | # An instance should not have a meaningful value parameter, but 222 | # sometimes does, particularly for string exceptions, such as 223 | # >>> raise string1, string2 # deprecated 224 | # 225 | # Clear these out first because issubtype(string1, SyntaxError) 226 | # would raise another exception and mask the original problem. 227 | if (isinstance(etype, BaseException) or 228 | isinstance(etype, types.InstanceType) or 229 | etype is None or type(etype) is str): 230 | return [_format_final_exc_line(etype, value)] 231 | 232 | stype = etype.__name__ 233 | 234 | if not issubclass(etype, SyntaxError): 235 | return [_format_final_exc_line(stype, value)] 236 | 237 | # It was a syntax error; show exactly where the problem was found. 238 | lines = [] 239 | try: 240 | msg, (filename, lineno, offset, badline) = value.args 241 | except Exception: 242 | pass 243 | else: 244 | filename = filename or "" 245 | lines.append(' File "%s", line %d\n' % (filename, lineno)) 246 | if badline is not None: 247 | lines.append(' %s\n' % badline.strip()) 248 | if offset is not None: 249 | caretspace = badline.rstrip('\n') 250 | offset = min(len(caretspace), offset) - 1 251 | caretspace = caretspace[:offset].lstrip() 252 | # non-space whitespace (likes tabs) must be kept for alignment 253 | caretspace = ((c.isspace() and c or ' ') for c in caretspace) 254 | lines.append(' %s^\n' % ''.join(caretspace)) 255 | value = msg 256 | 257 | lines.append(_format_final_exc_line(stype, value)) 258 | return lines 259 | 260 | def _format_final_exc_line(etype, value): 261 | """Return a list of a single line -- normal case for format_exception_only""" 262 | valuestr = _some_str(value) 263 | if value is None or not valuestr: 264 | line = "%s\n" % etype 265 | else: 266 | line = "%s: %s\n" % (etype, valuestr) 267 | return line 268 | 269 | def _some_str(value): 270 | try: 271 | return str(value) 272 | except Exception: 273 | pass 274 | try: 275 | value = unicode(value) 276 | return value.encode("ascii", "backslashreplace") 277 | except Exception: 278 | pass 279 | return '' % type(value).__name__ 280 | 281 | 282 | def print_exc(limit=None, file=None): 283 | """Shorthand for 'print_exception(sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file)'. 284 | (In fact, it uses sys.exc_info() to retrieve the same information 285 | in a thread-safe way.)""" 286 | if file is None: 287 | file = sys.stderr 288 | try: 289 | etype, value, tb = sys.exc_info() 290 | print_exception(etype, value, tb, limit, file) 291 | finally: 292 | etype = value = tb = None 293 | 294 | 295 | def format_exc(limit=None, with_vars=True): 296 | """Like print_exc() but return a string.""" 297 | try: 298 | etype, value, tb = sys.exc_info() 299 | return ''.join(format_exception(etype, value, tb, limit, with_vars)) 300 | finally: 301 | etype = value = tb = None 302 | 303 | 304 | def print_last(limit=None, file=None): 305 | """This is a shorthand for 'print_exception(sys.last_type, 306 | sys.last_value, sys.last_traceback, limit, file)'.""" 307 | if not hasattr(sys, "last_type"): 308 | raise ValueError("no last exception") 309 | if file is None: 310 | file = sys.stderr 311 | print_exception(sys.last_type, sys.last_value, sys.last_traceback, 312 | limit, file) 313 | 314 | 315 | def print_stack(f=None, limit=None, file=None, with_vars=True): 316 | """Print a stack trace from its invocation point. 317 | 318 | The optional 'f' argument can be used to specify an alternate 319 | stack frame at which to start. The optional 'limit' and 'file' 320 | arguments have the same meaning as for print_exception(). 321 | """ 322 | if f is None: 323 | try: 324 | raise ZeroDivisionError 325 | except ZeroDivisionError: 326 | f = sys.exc_info()[2].tb_frame.f_back 327 | print_list(extract_stack(f, limit), file, with_vars) 328 | 329 | def format_stack(f=None, limit=None, with_vars=True): 330 | """Shorthand for 'format_list(extract_stack(f, limit))'.""" 331 | if f is None: 332 | try: 333 | raise ZeroDivisionError 334 | except ZeroDivisionError: 335 | f = sys.exc_info()[2].tb_frame.f_back 336 | return format_list(extract_stack(f, limit), with_vars) 337 | 338 | def extract_stack(f=None, limit = None): 339 | """Extract the raw traceback from the current stack frame. 340 | 341 | The return value has the same format as for extract_tb(). The 342 | optional 'f' and 'limit' arguments have the same meaning as for 343 | print_stack(). Each item in the list is a quadruple (filename, 344 | line number, function name, text), and the entries are in order 345 | from oldest to newest stack frame. 346 | """ 347 | if f is None: 348 | try: 349 | raise ZeroDivisionError 350 | except ZeroDivisionError: 351 | f = sys.exc_info()[2].tb_frame.f_back 352 | if limit is None: 353 | if hasattr(sys, 'tracebacklimit'): 354 | limit = sys.tracebacklimit 355 | list = [] 356 | n = 0 357 | while f is not None and (limit is None or n < limit): 358 | lineno = f.f_lineno 359 | co = f.f_code 360 | filename = co.co_filename 361 | name = co.co_name 362 | linecache.checkcache(filename) 363 | line = linecache.getline(filename, lineno, f.f_globals) 364 | if line: line = line.strip() 365 | else: line = None 366 | locals = f.f_locals.items() 367 | list.append((filename, lineno, name, line, locals)) 368 | f = f.f_back 369 | n = n+1 370 | list.reverse() 371 | return list 372 | 373 | def tb_lineno(tb): 374 | """Calculate correct line number of traceback given in tb. 375 | 376 | Obsolete in 2.3. 377 | """ 378 | return tb.tb_lineno 379 | --------------------------------------------------------------------------------