13723.1.1
by Gary Poster
incremental commit |
1 |
# Copyright 2011 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
3 |
||
4 |
"""Replacement for some standard library traceback module functions.
|
|
5 |
||
6 |
These honor traceback supplements as defined in zope.exceptions.
|
|
7 |
"""
|
|
8 |
||
9 |
__metaclass__ = type |
|
10 |
__all__ = [ |
|
11 |
'extract_stack', |
|
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
12 |
'extract_tb', |
13723.1.1
by Gary Poster
incremental commit |
13 |
'format_list', |
14 |
'print_list', |
|
15 |
'print_stack', |
|
16 |
]
|
|
17 |
||
18 |
import linecache |
|
19 |
import sys |
|
20 |
import traceback |
|
21 |
||
14550.1.1
by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad |
22 |
|
13723.1.1
by Gary Poster
incremental commit |
23 |
DEBUG_EXCEPTION_FORMATTER = False |
13776.2.3
by Gary Poster
respond to review |
24 |
EXPLOSIVE_ERRORS = (SystemExit, MemoryError, KeyboardInterrupt) |
13723.1.1
by Gary Poster
incremental commit |
25 |
|
26 |
||
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
27 |
def _try_except(callable, *args, **kwargs): |
28 |
try: |
|
29 |
return callable(*args, **kwargs) |
|
13776.2.3
by Gary Poster
respond to review |
30 |
except EXPLOSIVE_ERRORS: |
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
31 |
raise
|
32 |
except: |
|
33 |
if DEBUG_EXCEPTION_FORMATTER: |
|
34 |
traceback.print_exc() |
|
35 |
# return None
|
|
36 |
||
37 |
||
13723.1.1
by Gary Poster
incremental commit |
38 |
def _get_frame(f): |
39 |
"if the frame is None, make one."
|
|
40 |
if f is None: |
|
41 |
try: |
|
42 |
raise ZeroDivisionError |
|
43 |
except ZeroDivisionError: |
|
44 |
f = sys.exc_info()[2].tb_frame.f_back.f_back |
|
45 |
return f |
|
46 |
||
47 |
||
48 |
def _fmt(string): |
|
49 |
"Return the string as deemed suitable for the extra information."
|
|
50 |
return ' - %s' % string |
|
51 |
||
52 |
||
53 |
def print_list(extracted_list, file=None): |
|
54 |
"""Print the list of tuples as returned by extract_tb() or
|
|
55 |
extract_stack() as a formatted stack trace to the given file."""
|
|
56 |
if file is None: |
|
57 |
file = sys.stderr |
|
58 |
for line in format_list(extracted_list): |
|
59 |
file.write(line) |
|
60 |
||
61 |
||
62 |
def format_list(extracted_list): |
|
63 |
"""Format a list of traceback entry tuples for printing.
|
|
64 |
||
65 |
Given a list of tuples as returned by extract_tb() or
|
|
66 |
extract_stack(), return a list of strings ready for printing.
|
|
67 |
Each string in the resulting list corresponds to the item with the
|
|
68 |
same index in the argument list. Each string ends in a newline;
|
|
69 |
the strings may contain internal newlines as well, for those items
|
|
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
70 |
whose source text line or supplement or info are not None.
|
13723.1.1
by Gary Poster
incremental commit |
71 |
"""
|
72 |
list = [] |
|
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
73 |
for filename, lineno, name, line, modname, supp, info in extracted_list: |
13723.1.1
by Gary Poster
incremental commit |
74 |
item = [] |
75 |
item.append( |
|
76 |
' File "%s", line %d, in %s' % (filename, lineno, name)) |
|
77 |
if line: |
|
78 |
item.append(' %s' % line.strip()) |
|
79 |
# The "supp" and "info" bits are adapted from zope.exceptions.
|
|
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
80 |
try: |
81 |
if supp: |
|
82 |
if supp['source_url']: |
|
83 |
item.append(_fmt(supp['source_url'])) |
|
84 |
if supp['line']: |
|
85 |
if supp['column']: |
|
86 |
item.append( |
|
87 |
_fmt('Line %(line)s, Column %(column)s' % supp)) |
|
88 |
else: |
|
89 |
item.append(_fmt('Line %(line)s' % supp)) |
|
90 |
elif supp['column']: |
|
91 |
item.append(_fmt('Column %(column)s' % supp)) |
|
92 |
if supp['expression']: |
|
93 |
item.append(_fmt('Expression: %(expression)s' % supp)) |
|
94 |
if supp['warnings']: |
|
95 |
for warning in supp['warnings']: |
|
96 |
item.append(_fmt('Warning: %s' % warning)) |
|
97 |
if supp['extra']: |
|
13723.1.5
by Gary Poster
pacify lint |
98 |
item.append(supp['extra']) # We do not include a prefix. |
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
99 |
if info: |
100 |
item.append(_fmt(info)) |
|
13776.2.3
by Gary Poster
respond to review |
101 |
except EXPLOSIVE_ERRORS: |
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
102 |
raise
|
103 |
except: |
|
104 |
# The values above may not stringify properly, or who knows what
|
|
105 |
# else. Be defensive.
|
|
106 |
if DEBUG_EXCEPTION_FORMATTER: |
|
107 |
traceback.print_exc() |
|
108 |
# else just swallow the exception.
|
|
13723.1.5
by Gary Poster
pacify lint |
109 |
item.append('') # This gives us a trailing newline. |
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
110 |
list.append('\n'.join(item)) |
13723.1.1
by Gary Poster
incremental commit |
111 |
return list |
112 |
||
113 |
||
114 |
def print_stack(f=None, limit=None, file=None): |
|
115 |
"""Print a stack trace from its invocation point.
|
|
116 |
||
117 |
The optional 'f' argument can be used to specify an alternate
|
|
118 |
stack frame at which to start. The optional 'limit' and 'file'
|
|
119 |
arguments have the same meaning as for print_exception().
|
|
120 |
"""
|
|
121 |
print_list(extract_stack(_get_frame(f), limit), file) |
|
122 |
||
123 |
||
124 |
def _get_limit(limit): |
|
125 |
"Return the limit or the globally-set limit, if any."
|
|
126 |
if limit is None: |
|
127 |
# stdlib uses hasattr here.
|
|
128 |
if hasattr(sys, 'tracebacklimit'): |
|
129 |
limit = sys.tracebacklimit |
|
130 |
return limit |
|
131 |
||
132 |
||
133 |
def _get_frame_data(f, lineno): |
|
134 |
"Given a frame and a lineno, return data for each item of extract_*."
|
|
135 |
# Adapted from zope.exceptions.
|
|
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
136 |
try_except = _try_except # This is a micro-optimization. |
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
137 |
modname = f.f_globals.get('__name__') |
13723.1.1
by Gary Poster
incremental commit |
138 |
# Output a traceback supplement, if any.
|
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
139 |
supplement_dict = info = None |
13723.1.1
by Gary Poster
incremental commit |
140 |
if '__traceback_supplement__' in f.f_locals: |
141 |
# Use the supplement defined in the function.
|
|
142 |
tbs = f.f_locals['__traceback_supplement__'] |
|
143 |
elif '__traceback_supplement__' in f.f_globals: |
|
144 |
# Use the supplement defined in the module.
|
|
145 |
tbs = f.f_globals['__traceback_supplement__'] |
|
146 |
else: |
|
147 |
tbs = None |
|
148 |
if tbs is not None: |
|
149 |
factory = tbs[0] |
|
150 |
args = tbs[1:] |
|
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
151 |
supplement = try_except(factory, *args) |
152 |
if supplement is not None: |
|
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
153 |
# It might be nice if supplements could be dicts, for simplicity.
|
154 |
# Historically, though, they are objects.
|
|
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
155 |
# We will turn the supplement into a dict of strings, so that we
|
156 |
# have "getInfo" pre-processed and so that we are not holding on
|
|
157 |
# to anything from the frame.
|
|
13723.1.1
by Gary Poster
incremental commit |
158 |
extra = None |
159 |
getInfo = getattr(supplement, 'getInfo', None) |
|
160 |
if getInfo is not None: |
|
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
161 |
extra = try_except(getInfo) |
162 |
extra = try_except(str, extra) if extra is not None else None |
|
163 |
warnings = [] |
|
164 |
# The outer try-except is for the iteration.
|
|
165 |
try: |
|
166 |
for warning in getattr(supplement, 'warnings', ()): |
|
167 |
if warning is not None: |
|
168 |
warning = try_except(str, warning) |
|
169 |
if warning is not None: |
|
170 |
warnings.append(warning) |
|
13776.2.3
by Gary Poster
respond to review |
171 |
except EXPLOSIVE_ERRORS: |
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
172 |
raise
|
173 |
except: |
|
174 |
if DEBUG_EXCEPTION_FORMATTER: |
|
175 |
traceback.print_exc() |
|
176 |
supplement_dict = dict(warnings=warnings, extra=extra) |
|
177 |
for key in ('source_url', 'line', 'column', 'expression'): |
|
178 |
value = getattr(supplement, key, None) |
|
179 |
if value is not None: |
|
180 |
value = try_except(str, value) |
|
181 |
supplement_dict[key] = value |
|
13723.1.1
by Gary Poster
incremental commit |
182 |
info = f.f_locals.get('__traceback_info__', None) |
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
183 |
info = try_except(str, info) if info is not None else None |
13723.1.1
by Gary Poster
incremental commit |
184 |
# End part adapted from zope.exceptions.
|
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
185 |
co = f.f_code |
186 |
filename = co.co_filename |
|
187 |
name = co.co_name |
|
188 |
linecache.checkcache(filename) |
|
189 |
line = linecache.getline(filename, lineno, f.f_globals) |
|
190 |
if line: |
|
191 |
line = line.strip() |
|
192 |
else: |
|
193 |
line = None |
|
194 |
return (filename, lineno, name, line, modname, supplement_dict, info) |
|
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
195 |
|
196 |
||
197 |
def extract_stack(f=None, limit=None): |
|
13723.1.1
by Gary Poster
incremental commit |
198 |
"""Extract the raw traceback from the current stack frame.
|
199 |
||
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
200 |
The return value has the same format as for extract_tb(). The optional
|
13723.1.5
by Gary Poster
pacify lint |
201 |
'f' and 'limit' arguments have the same meaning as for print_stack().
|
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
202 |
Each item in the list is a septuple (filename, line number, function name,
|
203 |
text, module name, optional supplement dict, optional info string), and
|
|
204 |
the entries are in order from oldest to newest stack frame.
|
|
13723.1.1
by Gary Poster
incremental commit |
205 |
"""
|
206 |
f = _get_frame(f) |
|
207 |
limit = _get_limit(limit) |
|
208 |
list = [] |
|
209 |
n = 0 |
|
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
210 |
get_frame_data = _get_frame_data # This is a micro-optimization. |
13723.1.1
by Gary Poster
incremental commit |
211 |
while f is not None and (limit is None or n < limit): |
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
212 |
list.append(get_frame_data(f, f.f_lineno)) |
13723.1.1
by Gary Poster
incremental commit |
213 |
f = f.f_back |
13723.1.5
by Gary Poster
pacify lint |
214 |
n = n + 1 |
13723.1.1
by Gary Poster
incremental commit |
215 |
list.reverse() |
216 |
return list |
|
217 |
||
218 |
||
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
219 |
def extract_tb(tb, limit=None): |
13723.1.1
by Gary Poster
incremental commit |
220 |
"""Return list of up to limit pre-processed entries from traceback.
|
221 |
||
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
222 |
This is useful for alternate formatting of stack traces. If 'limit' is
|
223 |
omitted or None, all entries are extracted. A pre-processed stack trace
|
|
224 |
entry is a sextuple (filename, line number, function name, text, module
|
|
225 |
name, optional supplement dict, optional info string) representing the
|
|
226 |
information that is printed for a stack trace. The text is a string with
|
|
227 |
leading and trailing whitespace stripped; if the source is not available
|
|
228 |
it is None. The supplement dict has keys 'source_url', 'line', 'column',
|
|
229 |
'expression', 'warnings' (an iterable), and 'extra', any of which may be
|
|
230 |
None.
|
|
13723.1.1
by Gary Poster
incremental commit |
231 |
"""
|
13723.1.3
by Gary Poster
tests and implementation for sqltrace profile code |
232 |
# zope.exceptions handles tracebacks. This function is implemented just
|
233 |
# to show how this module's patterns might be extended to tracebacks.
|
|
13723.1.1
by Gary Poster
incremental commit |
234 |
limit = _get_limit(limit) |
235 |
list = [] |
|
236 |
n = 0 |
|
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
237 |
get_frame_data = _get_frame_data # This is a micro-optimization. |
13723.1.1
by Gary Poster
incremental commit |
238 |
while tb is not None and (limit is None or n < limit): |
13776.2.1
by Gary Poster
fix ++profile++sqltrace for pages that have traceback hints that must be rendered immediately, like the root page of the site. |
239 |
list.append(get_frame_data(tb.tb_frame, tb.tb_lineno)) |
13723.1.1
by Gary Poster
incremental commit |
240 |
tb = tb.tb_next |
13723.1.5
by Gary Poster
pacify lint |
241 |
n = n + 1 |
13723.1.1
by Gary Poster
incremental commit |
242 |
return list |