root/galaxy-central/eggs/WebError-0.8a-py2.6.egg/weberror/exceptions/collector.py

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