1# Copyright 2015 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import math
16import os
17import sys
18import traceback
19
20
21def PrintFormattedException(msg=None):
22  exception_class, exception, tb = sys.exc_info()
23
24  def _GetFinalFrame(tb_level):
25    while tb_level.tb_next:
26      tb_level = tb_level.tb_next
27    return tb_level.tb_frame
28
29  processed_tb = traceback.extract_tb(tb)
30  frame = _GetFinalFrame(tb)
31  exception_list = traceback.format_exception_only(exception_class, exception)
32  exception_string = '\n'.join(l.strip() for l in exception_list)
33
34  if msg:
35    print >> sys.stderr
36    print >> sys.stderr, msg
37
38  _PrintFormattedTrace(processed_tb, frame, exception_string)
39
40def PrintFormattedFrame(frame, exception_string=None):
41  _PrintFormattedTrace(traceback.extract_stack(frame), frame, exception_string)
42
43
44def _PrintFormattedTrace(processed_tb, frame, exception_string=None):
45  """Prints an Exception in a more useful format than the default.
46  """
47  print >> sys.stderr
48
49  # Format the traceback.
50  base_dir = os.path.dirname(__file__)
51  print >> sys.stderr, 'Traceback (most recent call last):'
52  for filename, line, function, text in processed_tb:
53    filename = os.path.abspath(filename)
54    if filename.startswith(base_dir):
55      filename = filename[len(base_dir)+1:]
56    print >> sys.stderr, '  %s at %s:%d' % (function, filename, line)
57    print >> sys.stderr, '    %s' % text
58
59  # Format the exception.
60  if exception_string:
61    print >> sys.stderr, exception_string
62
63  # Format the locals.
64  local_variables = [(variable, value) for variable, value in
65                     frame.f_locals.iteritems() if variable != 'self']
66  print >> sys.stderr
67  print >> sys.stderr, 'Locals:'
68  if local_variables:
69    longest_variable = max(len(v) for v, _ in local_variables)
70    for variable, value in sorted(local_variables):
71      value = repr(value)
72      possibly_truncated_value = _AbbreviateMiddleOfString(value, ' ... ', 1024)
73      truncation_indication = ''
74      if len(possibly_truncated_value) != len(value):
75        truncation_indication = ' (truncated)'
76      print >> sys.stderr, '  %s: %s%s' % (variable.ljust(longest_variable + 1),
77                                           possibly_truncated_value,
78                                           truncation_indication)
79  else:
80    print >> sys.stderr, '  No locals!'
81
82  print >> sys.stderr
83  sys.stderr.flush()
84
85
86def _AbbreviateMiddleOfString(target, middle, max_length):
87  if max_length < 0:
88    raise ValueError('Must provide positive max_length')
89  if len(middle) > max_length:
90    raise ValueError('middle must not be greater than max_length')
91
92  if len(target) <= max_length:
93    return target
94  half_length = (max_length - len(middle)) / 2.
95  return (target[:int(math.floor(half_length))] + middle +
96          target[-int(math.ceil(half_length)):])
97