root/galaxy-central/eggs/Paste-1.6-py2.6.egg/paste/exceptions/collector.py

リビジョン 3, 18.9 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3##############################################################################
4#
5# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
6# All Rights Reserved.
7#
8# This software is subject to the provisions of the Zope Public License,
9# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
10# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
11# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
12# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
13# FOR A PARTICULAR PURPOSE.
14#
15##############################################################################
16## Originally zExceptions.ExceptionFormatter from Zope;
17## Modified by Ian Bicking, Imaginary Landscape, 2005
18"""
19An exception collector that finds traceback information plus
20supplements
21"""
22
23import sys
24import traceback
25import time
26try:
27    from cStringIO import StringIO
28except ImportError:
29    from StringIO import StringIO
30import linecache
31from paste.exceptions import serial_number_generator
32import warnings
33
34DEBUG_EXCEPTION_FORMATTER = True
35DEBUG_IDENT_PREFIX = 'E-'
36
37__all__ = ['collect_exception', 'ExceptionCollector']
38
39class ExceptionCollector(object):
40
41    """
42    Produces a data structure that can be used by formatters to
43    display exception reports.
44
45    Magic variables:
46
47    If you define one of these variables in your local scope, you can
48    add information to tracebacks that happen in that context.  This
49    allows applications to add all sorts of extra information about
50    the context of the error, including URLs, environmental variables,
51    users, hostnames, etc.  These are the variables we look for:
52
53    ``__traceback_supplement__``:
54        You can define this locally or globally (unlike all the other
55        variables, which must be defined locally).
56
57        ``__traceback_supplement__`` is a tuple of ``(factory, arg1,
58        arg2...)``.  When there is an exception, ``factory(arg1, arg2,
59        ...)`` is called, and the resulting object is inspected for
60        supplemental information.
61
62    ``__traceback_info__``:
63        This information is added to the traceback, usually fairly
64        literally.
65
66    ``__traceback_hide__``:
67        If set and true, this indicates that the frame should be
68        hidden from abbreviated tracebacks.  This way you can hide
69        some of the complexity of the larger framework and let the
70        user focus on their own errors.
71
72        By setting it to ``'before'``, all frames before this one will
73        be thrown away.  By setting it to ``'after'`` then all frames
74        after this will be thrown away until ``'reset'`` is found.  In
75        each case the frame where it is set is included, unless you
76        append ``'_and_this'`` to the value (e.g.,
77        ``'before_and_this'``).
78
79        Note that formatters will ignore this entirely if the frame
80        that contains the error wouldn't normally be shown according
81        to these rules.
82
83    ``__traceback_reporter__``:
84        This should be a reporter object (see the reporter module),
85        or a list/tuple of reporter objects.  All reporters found this
86        way will be given the exception, innermost first.
87
88    ``__traceback_decorator__``:
89        This object (defined in a local or global scope) will get the
90        result of this function (the CollectedException defined
91        below).  It may modify this object in place, or return an
92        entirely new object.  This gives the object the ability to
93        manipulate the traceback arbitrarily.
94
95    The actually interpretation of these values is largely up to the
96    reporters and formatters.
97   
98    ``collect_exception(*sys.exc_info())`` will return an object with
99    several attributes:
100
101    ``frames``:
102        A list of frames
103    ``exception_formatted``:
104        The formatted exception, generally a full traceback
105    ``exception_type``:
106        The type of the exception, like ``ValueError``
107    ``exception_value``:
108        The string value of the exception, like ``'x not in list'``
109    ``identification_code``:
110        A hash of the exception data meant to identify the general
111        exception, so that it shares this code with other exceptions
112        that derive from the same problem.  The code is a hash of
113        all the module names and function names in the traceback,
114        plus exception_type.  This should be shown to users so they
115        can refer to the exception later. (@@: should it include a
116        portion that allows identification of the specific instance
117        of the exception as well?)
118       
119    The list of frames goes innermost first.  Each frame has these
120    attributes; some values may be None if they could not be
121    determined.
122
123    ``modname``:
124        the name of the module
125    ``filename``:
126        the filename of the module
127    ``lineno``:
128        the line of the error
129    ``revision``:
130        the contents of __version__ or __revision__
131    ``name``:
132        the function name
133    ``supplement``:
134        an object created from ``__traceback_supplement__``
135    ``supplement_exception``:
136        a simple traceback of any exception ``__traceback_supplement__``
137        created
138    ``traceback_info``:
139        the str() of any ``__traceback_info__`` variable found in the local
140        scope (@@: should it str()-ify it or not?)
141    ``traceback_hide``:
142        the value of any ``__traceback_hide__`` variable
143    ``traceback_log``:
144        the value of any ``__traceback_log__`` variable
145   
146
147    ``__traceback_supplement__`` is thrown away, but a fixed
148    set of attributes are captured; each of these attributes is
149    optional.
150
151    ``object``:
152        the name of the object being visited
153    ``source_url``:
154        the original URL requested
155    ``line``:
156        the line of source being executed (for interpreters, like ZPT)
157    ``column``:
158        the column of source being executed
159    ``expression``:
160        the expression being evaluated (also for interpreters)
161    ``warnings``:
162        a list of (string) warnings to be displayed
163    ``getInfo``:
164        a function/method that takes no arguments, and returns a string
165        describing any extra information
166    ``extraData``:
167        a function/method that takes no arguments, and returns a
168        dictionary.  The contents of this dictionary will not be
169        displayed in the context of the traceback, but globally for
170        the exception.  Results will be grouped by the keys in the
171        dictionaries (which also serve as titles).  The keys can also
172        be tuples of (importance, title); in this case the importance
173        should be ``important`` (shows up at top), ``normal`` (shows
174        up somewhere; unspecified), ``supplemental`` (shows up at
175        bottom), or ``extra`` (shows up hidden or not at all).
176
177    These are used to create an object with attributes of the same
178    names (``getInfo`` becomes a string attribute, not a method).
179    ``__traceback_supplement__`` implementations should be careful to
180    produce values that are relatively static and unlikely to cause
181    further errors in the reporting system -- any complex
182    introspection should go in ``getInfo()`` and should ultimately
183    return a string.
184
185    Note that all attributes are optional, and under certain
186    circumstances may be None or may not exist at all -- the collector
187    can only do a best effort, but must avoid creating any exceptions
188    itself.
189
190    Formatters may want to use ``__traceback_hide__`` as a hint to
191    hide frames that are part of the 'framework' or underlying system.
192    There are a variety of rules about special values for this
193    variables that formatters should be aware of.
194   
195    TODO:
196
197    More attributes in __traceback_supplement__?  Maybe an attribute
198    that gives a list of local variables that should also be
199    collected?  Also, attributes that would be explicitly meant for
200    the entire request, not just a single frame.  Right now some of
201    the fixed set of attributes (e.g., source_url) are meant for this
202    use, but there's no explicit way for the supplement to indicate
203    new values, e.g., logged-in user, HTTP referrer, environment, etc.
204    Also, the attributes that do exist are Zope/Web oriented.
205
206    More information on frames?  cgitb, for instance, produces
207    extensive information on local variables.  There exists the
208    possibility that getting this information may cause side effects,
209    which can make debugging more difficult; but it also provides
210    fodder for post-mortem debugging.  However, the collector is not
211    meant to be configurable, but to capture everything it can and let
212    the formatters be configurable.  Maybe this would have to be a
213    configuration value, or maybe it could be indicated by another
214    magical variable (which would probably mean 'show all local
215    variables below this frame')
216    """
217
218    show_revisions = 0
219
220    def __init__(self, limit=None):
221        self.limit = limit
222
223    def getLimit(self):
224        limit = self.limit
225        if limit is None:
226            limit = getattr(sys, 'tracebacklimit', None)
227        return limit
228
229    def getRevision(self, globals):
230        if not self.show_revisions:
231            return None
232        revision = globals.get('__revision__', None)
233        if revision is None:
234            # Incorrect but commonly used spelling
235            revision = globals.get('__version__', None)
236
237        if revision is not None:
238            try:
239                revision = str(revision).strip()
240            except:
241                revision = '???'
242        return revision
243
244    def collectSupplement(self, supplement, tb):
245        result = {}
246
247        for name in ('object', 'source_url', 'line', 'column',
248                     'expression', 'warnings'):
249            result[name] = getattr(supplement, name, None)
250
251        func = getattr(supplement, 'getInfo', None)
252        if func:
253            result['info'] = func()
254        else:
255            result['info'] = None
256        func = getattr(supplement, 'extraData', None)
257        if func:
258            result['extra'] = func()
259        else:
260            result['extra'] = None
261        return SupplementaryData(**result)
262
263    def collectLine(self, tb, extra_data):
264        f = tb.tb_frame
265        lineno = tb.tb_lineno
266        co = f.f_code
267        filename = co.co_filename
268        name = co.co_name
269        globals = f.f_globals
270        locals = f.f_locals
271        if not hasattr(locals, 'has_key'):
272            # Something weird about this frame; it's not a real dict
273            warnings.warn(
274                "Frame %s has an invalid locals(): %r" % (
275                globals.get('__name__', 'unknown'), locals))
276            locals = {}
277        data = {}
278        data['modname'] = globals.get('__name__', None)
279        data['filename'] = filename
280        data['lineno'] = lineno
281        data['revision'] = self.getRevision(globals)
282        data['name'] = name
283        data['tbid'] = id(tb)
284
285        # Output a traceback supplement, if any.
286        if locals.has_key('__traceback_supplement__'):
287            # Use the supplement defined in the function.
288            tbs = locals['__traceback_supplement__']
289        elif globals.has_key('__traceback_supplement__'):
290            # Use the supplement defined in the module.
291            # This is used by Scripts (Python).
292            tbs = globals['__traceback_supplement__']
293        else:
294            tbs = None
295        if tbs is not None:
296            factory = tbs[0]
297            args = tbs[1:]
298            try:
299                supp = factory(*args)
300                data['supplement'] = self.collectSupplement(supp, tb)
301                if data['supplement'].extra:
302                    for key, value in data['supplement'].extra.items():
303                        extra_data.setdefault(key, []).append(value)
304            except:
305                if DEBUG_EXCEPTION_FORMATTER:
306                    out = StringIO()
307                    traceback.print_exc(file=out)
308                    text = out.getvalue()
309                    data['supplement_exception'] = text
310                # else just swallow the exception.
311
312        try:
313            tbi = locals.get('__traceback_info__', None)
314            if tbi is not None:
315                data['traceback_info'] = str(tbi)
316        except:
317            pass
318
319        marker = []
320        for name in ('__traceback_hide__', '__traceback_log__',
321                     '__traceback_decorator__'):
322            try:
323                tbh = locals.get(name, globals.get(name, marker))
324                if tbh is not marker:
325                    data[name[2:-2]] = tbh
326            except:
327                pass
328
329        return data
330
331    def collectExceptionOnly(self, etype, value):
332        return traceback.format_exception_only(etype, value)
333
334    def collectException(self, etype, value, tb, limit=None):
335        # The next line provides a way to detect recursion.
336        __exception_formatter__ = 1
337        frames = []
338        ident_data = []
339        traceback_decorators = []
340        if limit is None:
341            limit = self.getLimit()
342        n = 0
343        extra_data = {}
344        while tb is not None and (limit is None or n < limit):
345            if tb.tb_frame.f_locals.get('__exception_formatter__'):
346                # Stop recursion. @@: should make a fake ExceptionFrame
347                frames.append('(Recursive formatException() stopped)\n')
348                break
349            data = self.collectLine(tb, extra_data)
350            frame = ExceptionFrame(**data)
351            frames.append(frame)
352            if frame.traceback_decorator is not None:
353                traceback_decorators.append(frame.traceback_decorator)
354            ident_data.append(frame.modname or '?')
355            ident_data.append(frame.name or '?')
356            tb = tb.tb_next
357            n = n + 1
358        ident_data.append(str(etype))
359        ident = serial_number_generator.hash_identifier(
360            ' '.join(ident_data), length=5, upper=True,
361            prefix=DEBUG_IDENT_PREFIX)
362
363        result = CollectedException(
364            frames=frames,
365            exception_formatted=self.collectExceptionOnly(etype, value),
366            exception_type=etype,
367            exception_value=str(value),
368            identification_code=ident,
369            date=time.localtime(),
370            extra_data=extra_data)
371        if etype is ImportError:
372            extra_data[('important', 'sys.path')] = [sys.path]
373        for decorator in traceback_decorators:
374            try:
375                new_result = decorator(result)
376                if new_result is not None:
377                    result = new_result
378            except:
379                pass
380        return result
381
382limit = 200
383
384class Bunch(object):
385
386    """
387    A generic container
388    """
389
390    def __init__(self, **attrs):
391        for name, value in attrs.items():
392            setattr(self, name, value)
393
394    def __repr__(self):
395        name = '<%s ' % self.__class__.__name__
396        name += ' '.join(['%s=%r' % (name, str(value)[:30])
397                          for name, value in self.__dict__.items()
398                          if not name.startswith('_')])
399        return name + '>'
400
401class CollectedException(Bunch):
402    """
403    This is the result of collection the exception; it contains copies
404    of data of interest.
405    """
406    # A list of frames (ExceptionFrame instances), innermost last:
407    frames = []
408    # The result of traceback.format_exception_only; this looks
409    # like a normal traceback you'd see in the interactive interpreter
410    exception_formatted = None
411    # The *string* representation of the type of the exception
412    # (@@: should we give the # actual class? -- we can't keep the
413    # actual exception around, but the class should be safe)
414    # Something like 'ValueError'
415    exception_type = None
416    # The string representation of the exception, from ``str(e)``.
417    exception_value = None
418    # An identifier which should more-or-less classify this particular
419    # exception, including where in the code it happened.
420    identification_code = None
421    # The date, as time.localtime() returns:
422    date = None
423    # A dictionary of supplemental data:
424    extra_data = {}
425
426class SupplementaryData(Bunch):
427    """
428    The result of __traceback_supplement__.  We don't keep the
429    supplement object around, for fear of GC problems and whatnot.
430    (@@: Maybe I'm being too superstitious about copying only specific
431    information over)
432    """
433
434    # These attributes are copied from the object, or left as None
435    # if the object doesn't have these attributes:
436    object = None
437    source_url = None
438    line = None
439    column = None
440    expression = None
441    warnings = None
442    # This is the *return value* of supplement.getInfo():
443    info = None
444
445class ExceptionFrame(Bunch):
446    """
447    This represents one frame of the exception.  Each frame is a
448    context in the call stack, typically represented by a line
449    number and module name in the traceback.
450    """
451
452    # The name of the module; can be None, especially when the code
453    # isn't associated with a module.
454    modname = None
455    # The filename (@@: when no filename, is it None or '?'?)
456    filename = None
457    # Line number
458    lineno = None
459    # The value of __revision__ or __version__ -- but only if
460    # show_revision = True (by defaut it is false).  (@@: Why not
461    # collect this?)
462    revision = None
463    # The name of the function with the error (@@: None or '?' when
464    # unknown?)
465    name = None
466    # A SupplementaryData object, if __traceback_supplement__ was found
467    # (and produced no errors)
468    supplement = None
469    # If accessing __traceback_supplement__ causes any error, the
470    # plain-text traceback is stored here
471    supplement_exception = None
472    # The str() of any __traceback_info__ value found
473    traceback_info = None
474    # The value of __traceback_hide__
475    traceback_hide = False
476    # The value of __traceback_decorator__
477    traceback_decorator = None
478    # The id() of the traceback scope, can be used to reference the
479    # scope for use elsewhere
480    tbid = None
481
482    def get_source_line(self, context=0):
483        """
484        Return the source of the current line of this frame.  You
485        probably want to .strip() it as well, as it is likely to have
486        leading whitespace.
487
488        If context is given, then that many lines on either side will
489        also be returned.  E.g., context=1 will give 3 lines.
490        """
491        if not self.filename or not self.lineno:
492            return None
493        lines = []
494        for lineno in range(self.lineno-context, self.lineno+context+1):
495            lines.append(linecache.getline(self.filename, lineno))
496        return ''.join(lines)
497       
498if hasattr(sys, 'tracebacklimit'):
499    limit = min(limit, sys.tracebacklimit)
500
501col = ExceptionCollector()
502
503def collect_exception(t, v, tb, limit=None):
504    """
505    Collection an exception from ``sys.exc_info()``.
506   
507    Use like::
508
509      try:
510          blah blah
511      except:
512          exc_data = collect_exception(*sys.exc_info())
513    """
514    return col.collectException(t, v, tb, limit=limit)
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。