1# Copyright 2014 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Print prettier and more detailed exceptions.""" 6 7import logging 8import math 9import os 10import sys 11import traceback 12 13from telemetry.core import exceptions 14from telemetry.core import util 15 16 17def PrintFormattedException(exception_class=None, exception=None, tb=None, 18 msg=None): 19 logging.info('Try printing formatted exception: %s %s %s' % 20 (exception_class, exception, tb)) 21 assert bool(exception_class) == bool(exception) == bool(tb), ( 22 'Must specify all or none of exception_class, exception, and tb') 23 24 if not exception_class: 25 exception_class, exception, tb = sys.exc_info() 26 27 if exception_class == exceptions.IntentionalException: 28 return 29 30 def _GetFinalFrame(tb_level): 31 while tb_level.tb_next: 32 tb_level = tb_level.tb_next 33 return tb_level.tb_frame 34 35 processed_tb = traceback.extract_tb(tb) 36 frame = _GetFinalFrame(tb) 37 exception_list = traceback.format_exception_only(exception_class, exception) 38 exception_string = '\n'.join(l.strip() for l in exception_list) 39 40 if msg: 41 print >> sys.stderr 42 print >> sys.stderr, msg 43 44 _PrintFormattedTrace(processed_tb, frame, exception_string) 45 46 47def PrintFormattedFrame(frame, exception_string=None): 48 _PrintFormattedTrace(traceback.extract_stack(frame), frame, exception_string) 49 50 51def _PrintFormattedTrace(processed_tb, frame, exception_string=None): 52 """Prints an Exception in a more useful format than the default. 53 54 TODO(tonyg): Consider further enhancements. For instance: 55 - Report stacks to maintainers like depot_tools does. 56 - Add a debug flag to automatically start pdb upon exception. 57 """ 58 print >> sys.stderr 59 60 # Format the traceback. 61 base_dir = os.path.abspath(util.GetChromiumSrcDir()) 62 print >> sys.stderr, 'Traceback (most recent call last):' 63 for filename, line, function, text in processed_tb: 64 filename = os.path.abspath(filename) 65 if filename.startswith(base_dir): 66 filename = filename[len(base_dir)+1:] 67 print >> sys.stderr, ' %s at %s:%d' % (function, filename, line) 68 print >> sys.stderr, ' %s' % text 69 70 # Format the exception. 71 if exception_string: 72 print >> sys.stderr, exception_string 73 74 # Format the locals. 75 local_variables = [(variable, value) for variable, value in 76 frame.f_locals.iteritems() if variable != 'self'] 77 print >> sys.stderr 78 print >> sys.stderr, 'Locals:' 79 if local_variables: 80 longest_variable = max(len(v) for v, _ in local_variables) 81 for variable, value in sorted(local_variables): 82 value = repr(value) 83 possibly_truncated_value = _AbbreviateMiddleOfString(value, ' ... ', 1024) 84 truncation_indication = '' 85 if len(possibly_truncated_value) != len(value): 86 truncation_indication = ' (truncated)' 87 print >> sys.stderr, ' %s: %s%s' % (variable.ljust(longest_variable + 1), 88 possibly_truncated_value, 89 truncation_indication) 90 else: 91 print >> sys.stderr, ' No locals!' 92 93 print >> sys.stderr 94 sys.stderr.flush() 95 96 97def _AbbreviateMiddleOfString(target, middle, max_length): 98 if max_length < 0: 99 raise ValueError('Must provide positive max_length') 100 if len(middle) > max_length: 101 raise ValueError('middle must not be greater than max_length') 102 103 if len(target) <= max_length: 104 return target 105 half_length = (max_length - len(middle)) / 2. 106 return (target[:int(math.floor(half_length))] + middle + 107 target[-int(math.ceil(half_length)):]) 108