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

リビジョン 3, 15.1 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"""
5Error handler middleware
6"""
7import sys
8import traceback
9import cgi
10try:
11    from cStringIO import StringIO
12except ImportError:
13    from StringIO import StringIO
14from weberror.exceptions import formatter, collector, reporter
15from paste import wsgilib
16from paste import request
17
18__all__ = ['ErrorMiddleware', 'handle_exception']
19
20class _NoDefault(object):
21    def __repr__(self):
22        return '<NoDefault>'
23NoDefault = _NoDefault()
24
25class ErrorMiddleware(object):
26
27    """
28    Error handling middleware
29   
30    Usage::
31
32        error_caching_wsgi_app = ErrorMiddleware(wsgi_app)
33
34    Settings:
35
36      ``debug``:
37          If true, then tracebacks will be shown in the browser.
38
39      ``error_email``:
40          an email address (or list of addresses) to send exception
41          reports to
42
43      ``error_log``:
44          a filename to append tracebacks to
45
46      ``show_exceptions_in_wsgi_errors``:
47          If true, then errors will be printed to ``wsgi.errors``
48          (frequently a server error log, or stderr).
49
50      ``from_address``, ``smtp_server``, ``error_subject_prefix``:
51          variables to control the emailed exception reports
52
53      ``error_message``:
54          When debug mode is off, the error message to show to users.
55
56      ``xmlhttp_key``:
57          When this key (default ``_``) is in the request GET variables
58          (not POST!), expect that this is an XMLHttpRequest, and the
59          response should be more minimal; it should not be a complete
60          HTML page.
61
62    Environment Configuration:
63   
64      ``paste.throw_errors``:
65          If this setting in the request environment is true, then this
66          middleware is disabled. This can be useful in a testing situation
67          where you don't want errors to be caught and transformed.
68
69      ``paste.expected_exceptions``:
70          When this middleware encounters an exception listed in this
71          environment variable and when the ``start_response`` has not
72          yet occurred, the exception will be re-raised instead of being
73          caught.  This should generally be set by middleware that may
74          (but probably shouldn't be) installed above this middleware,
75          and wants to get certain exceptions.  Exceptions raised after
76          ``start_response`` have been called are always caught since
77          by definition they are no longer expected.
78
79    """
80
81    def __init__(self, application, global_conf=None,
82                 debug=NoDefault,
83                 error_email=None,
84                 error_log=None,
85                 show_exceptions_in_wsgi_errors=NoDefault,
86                 from_address=None,
87                 smtp_server=None,
88                 error_subject_prefix=None,
89                 error_message=None,
90                 xmlhttp_key=None):
91        from paste.util import converters
92        self.application = application
93        # @@: global_conf should be handled elsewhere in a separate
94        # function for the entry point
95        if global_conf is None:
96            global_conf = {}
97        if debug is NoDefault:
98            debug = converters.asbool(global_conf.get('debug'))
99        if show_exceptions_in_wsgi_errors is NoDefault:
100            show_exceptions_in_wsgi_errors = converters.asbool(global_conf.get('show_exceptions_in_wsgi_errors'))
101        self.debug_mode = converters.asbool(debug)
102        if error_email is None:
103            error_email = (global_conf.get('error_email')
104                           or global_conf.get('admin_email')
105                           or global_conf.get('webmaster_email')
106                           or global_conf.get('sysadmin_email'))
107        self.error_email = converters.aslist(error_email)
108        self.error_log = error_log
109        self.show_exceptions_in_wsgi_errors = show_exceptions_in_wsgi_errors
110        if from_address is None:
111            from_address = global_conf.get('error_from_address', 'errors@localhost')
112        self.from_address = from_address
113        if smtp_server is None:
114            smtp_server = global_conf.get('smtp_server', 'localhost')
115        self.smtp_server = smtp_server
116        self.error_subject_prefix = error_subject_prefix or ''
117        if error_message is None:
118            error_message = global_conf.get('error_message')
119        self.error_message = error_message
120        if xmlhttp_key is None:
121            xmlhttp_key = global_conf.get('xmlhttp_key', '_')
122        self.xmlhttp_key = xmlhttp_key
123           
124    def __call__(self, environ, start_response):
125        """
126        The WSGI application interface.
127        """
128        # We want to be careful about not sending headers twice,
129        # and the content type that the app has committed to (if there
130        # is an exception in the iterator body of the response)
131        if environ.get('paste.throw_errors'):
132            return self.application(environ, start_response)
133        environ['paste.throw_errors'] = True
134
135        try:
136            __traceback_supplement__ = Supplement, self, environ
137            app_iter = self.application(environ, start_response)
138            return self.make_catching_iter(app_iter, environ)
139        except:
140            exc_info = sys.exc_info()
141            try:
142                start_response('500 Internal Server Error',
143                               [('content-type', 'text/html')],
144                               exc_info)
145                # @@: it would be nice to deal with bad content types here
146                response = self.exception_handler(exc_info, environ)
147                return [response]
148            finally:
149                # clean up locals...
150                exc_info = None
151
152    def make_catching_iter(self, app_iter, environ):
153        if isinstance(app_iter, (list, tuple)):
154            # These don't raise
155            return app_iter
156        return CatchingIter(app_iter, environ, self)
157
158    def exception_handler(self, exc_info, environ):
159        simple_html_error = False
160        if self.xmlhttp_key:
161            get_vars = wsgilib.parse_querystring(environ)
162            if dict(get_vars).get(self.xmlhttp_key):
163                simple_html_error = True
164        return handle_exception(
165            exc_info, environ['wsgi.errors'],
166            html=True,
167            debug_mode=self.debug_mode,
168            error_email=self.error_email,
169            error_log=self.error_log,
170            show_exceptions_in_wsgi_errors=self.show_exceptions_in_wsgi_errors,
171            error_email_from=self.from_address,
172            smtp_server=self.smtp_server,
173            error_subject_prefix=self.error_subject_prefix,
174            error_message=self.error_message,
175            simple_html_error=simple_html_error)
176
177class CatchingIter(object):
178
179    """
180    A wrapper around the application iterator that will catch
181    exceptions raised by the a generator, or by the close method, and
182    display or report as necessary.
183    """
184
185    def __init__(self, app_iter, environ, error_middleware):
186        self.app_iterable = app_iter
187        self.app_iterator = iter(app_iter)
188        self.environ = environ
189        self.error_middleware = error_middleware
190        self.closed = False
191
192    def __iter__(self):
193        return self
194
195    def next(self):
196        __traceback_supplement__ = (
197            Supplement, self.error_middleware, self.environ)
198        if self.closed:
199            raise StopIteration
200        try:
201            return self.app_iterator.next()
202        except StopIteration:
203            self.closed = True
204            close_response = self._close()
205            if close_response is not None:
206                return close_response
207            else:
208                raise StopIteration
209        except:
210            self.closed = True
211            close_response = self._close()
212            response = self.error_middleware.exception_handler(
213                sys.exc_info(), self.environ)
214            if close_response is not None:
215                response += (
216                    '<hr noshade>Error in .close():<br>%s'
217                    % close_response)
218            return response
219
220    def close(self):
221        # This should at least print something to stderr if the
222        # close method fails at this point
223        if not self.closed:
224            self._close()
225
226    def _close(self):
227        """Close and return any error message"""
228        if not hasattr(self.app_iterable, 'close'):
229            return None
230        try:
231            self.app_iterable.close()
232            return None
233        except:
234            close_response = self.error_middleware.exception_handler(
235                sys.exc_info(), self.environ)
236            return close_response
237
238
239class Supplement(object):
240    """This is a supplement used to display standard WSGI information
241    in the traceback.
242   
243    Additional configuration information can be added under a
244    Configuration section by populating the environ['weberror.config']
245    variable with a dictionary to include.
246   
247    """
248    def __init__(self, middleware, environ):
249        self.middleware = middleware
250        self.environ = environ
251        self.source_url = request.construct_url(environ)
252
253    def extraData(self):
254        data = {}
255        cgi_vars = data[('extra', 'CGI Variables')] = {}
256        wsgi_vars = data[('extra', 'WSGI Variables')] = {}
257        hide_vars = ['paste.config', 'wsgi.errors', 'wsgi.input',
258                     'wsgi.multithread', 'wsgi.multiprocess',
259                     'wsgi.run_once', 'wsgi.version',
260                     'wsgi.url_scheme']
261        for name, value in self.environ.items():
262            if name.upper() == name:
263                if value:
264                    cgi_vars[name] = value
265            elif name not in hide_vars:
266                wsgi_vars[name] = value
267        if self.environ['wsgi.version'] != (1, 0):
268            wsgi_vars['wsgi.version'] = self.environ['wsgi.version']
269        proc_desc = tuple([int(bool(self.environ[key]))
270                           for key in ('wsgi.multiprocess',
271                                       'wsgi.multithread',
272                                       'wsgi.run_once')])
273        wsgi_vars['wsgi process'] = self.process_combos[proc_desc]
274        wsgi_vars['application'] = self.middleware.application
275        if 'weberror.config' in self.environ:
276            data[('extra', 'Configuration')] = dict(self.environ['weberror.config'])
277        return data
278
279    process_combos = {
280        # multiprocess, multithread, run_once
281        (0, 0, 0): 'Non-concurrent server',
282        (0, 1, 0): 'Multithreaded',
283        (1, 0, 0): 'Multiprocess',
284        (1, 1, 0): 'Multi process AND threads (?)',
285        (0, 0, 1): 'Non-concurrent CGI',
286        (0, 1, 1): 'Multithread CGI (?)',
287        (1, 0, 1): 'CGI',
288        (1, 1, 1): 'Multi thread/process CGI (?)',
289        }
290   
291def handle_exception(exc_info, error_stream, html=True,
292                     debug_mode=False,
293                     error_email=None,
294                     error_log=None,
295                     show_exceptions_in_wsgi_errors=False,
296                     error_email_from='errors@localhost',
297                     smtp_server='localhost',
298                     error_subject_prefix='',
299                     error_message=None,
300                     simple_html_error=False,
301                     ):
302    """
303    For exception handling outside of a web context
304
305    Use like::
306
307        import sys
308        import paste
309        import paste.error_middleware
310        try:
311            do stuff
312        except:
313            paste.error_middleware.exception_handler(
314                sys.exc_info(), paste.CONFIG, sys.stderr, html=False)
315
316    If you want to report, but not fully catch the exception, call
317    ``raise`` after ``exception_handler``, which (when given no argument)
318    will reraise the exception.
319    """
320    reported = False
321    exc_data = collector.collect_exception(*exc_info)
322    extra_data = ''
323    if error_email:
324        rep = reporter.EmailReporter(
325            to_addresses=error_email,
326            from_address=error_email_from,
327            smtp_server=smtp_server,
328            subject_prefix=error_subject_prefix)
329        rep_err = send_report(rep, exc_data, html=html)
330        if rep_err:
331            extra_data += rep_err
332        else:
333            reported = True
334    if error_log:
335        rep = reporter.LogReporter(
336            filename=error_log)
337        rep_err = send_report(rep, exc_data, html=html)
338        if rep_err:
339            extra_data += rep_err
340        else:
341            reported = True
342    if show_exceptions_in_wsgi_errors:
343        rep = reporter.FileReporter(
344            file=error_stream)
345        rep_err = send_report(rep, exc_data, html=html)
346        if rep_err:
347            extra_data += rep_err
348        else:
349            reported = True
350    else:
351        error_stream.write('Error - %s: %s\n' % (
352            exc_data.exception_type, exc_data.exception_value))
353    if html:
354        if debug_mode and simple_html_error:
355            return_error = formatter.format_html(
356                exc_data, include_hidden_frames=False,
357                include_reusable=False, show_extra_data=False)
358            reported = True
359        elif debug_mode and not simple_html_error:
360            error_html = formatter.format_html(
361                exc_data,
362                include_hidden_frames=True,
363                include_reusable=False)
364            head_html = ''
365            return_error = error_template(
366                head_html, error_html, extra_data)
367            extra_data = ''
368            reported = True
369        else:
370            msg = error_message or '''
371            An error occurred.  See the error logs for more information.
372            (Turn debug on to display exception reports here)
373            '''
374            return_error = error_template('', msg, '')
375    else:
376        return_error = None
377    if not reported and error_stream:
378        err_report = formatter.format_text(exc_data, show_hidden_frames=True)[0]
379        err_report += '\n' + '-'*60 + '\n'
380        error_stream.write(err_report)
381    if extra_data:
382        error_stream.write(extra_data)
383    return return_error
384
385def send_report(rep, exc_data, html=True):
386    try:
387        rep.report(exc_data)
388    except:
389        output = StringIO()
390        traceback.print_exc(file=output)
391        if html:
392            return """
393            <p>Additionally an error occurred while sending the %s report:
394
395            <pre>%s</pre>
396            </p>""" % (
397                cgi.escape(str(rep)), output.getvalue())
398        else:
399            return (
400                "Additionally an error occurred while sending the "
401                "%s report:\n%s" % (str(rep), output.getvalue()))
402    else:
403        return ''
404
405def error_template(head_html, exception, extra):
406    return '''
407    <html>
408    <head>
409    <title>Server Error</title>
410    %s
411    </head>
412    <body>
413    <h1>Server Error</h1>
414    %s
415    %s
416    </body>
417    </html>''' % (head_html, exception, extra)
418
419def make_error_middleware(app, global_conf, **kw):
420    return ErrorMiddleware(app, global_conf=global_conf, **kw)
421
422doc_lines = ErrorMiddleware.__doc__.splitlines(True)
423for i in range(len(doc_lines)):
424    if doc_lines[i].strip().startswith('Settings'):
425        make_error_middleware.__doc__ = ''.join(doc_lines[i:])
426        break
427del i, doc_lines
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。