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