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

リビジョン 3, 15.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"""
5Error handler middleware
6"""
7import sys
8import traceback
9import cgi
10try:
11    from cStringIO import StringIO
12except ImportError:
13    from StringIO import StringIO
14from paste.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``, ``smtp_username``, ``smtp_password``, ``smtp_use_tls``:
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                 smtp_username=None,
89                 smtp_password=None,
90                 smtp_use_tls=False,
91                 error_subject_prefix=None,
92                 error_message=None,
93                 xmlhttp_key=None):
94        from paste.util import converters
95        self.application = application
96        # @@: global_conf should be handled elsewhere in a separate
97        # function for the entry point
98        if global_conf is None:
99            global_conf = {}
100        if debug is NoDefault:
101            debug = converters.asbool(global_conf.get('debug'))
102        if show_exceptions_in_wsgi_errors is NoDefault:
103            show_exceptions_in_wsgi_errors = converters.asbool(global_conf.get('show_exceptions_in_wsgi_errors'))
104        self.debug_mode = converters.asbool(debug)
105        if error_email is None:
106            error_email = (global_conf.get('error_email')
107                           or global_conf.get('admin_email')
108                           or global_conf.get('webmaster_email')
109                           or global_conf.get('sysadmin_email'))
110        self.error_email = converters.aslist(error_email)
111        self.error_log = error_log
112        self.show_exceptions_in_wsgi_errors = show_exceptions_in_wsgi_errors
113        if from_address is None:
114            from_address = global_conf.get('error_from_address', 'errors@localhost')
115        self.from_address = from_address
116        if smtp_server is None:
117            smtp_server = global_conf.get('smtp_server', 'localhost')
118        self.smtp_server = smtp_server
119        self.smtp_username = smtp_username or global_conf.get('smtp_username')
120        self.smtp_password = smtp_password or global_conf.get('smtp_password')
121        self.smtp_use_tls = smtp_use_tls or converters.asbool(global_conf.get('smtp_use_tls'))
122        self.error_subject_prefix = error_subject_prefix or ''
123        if error_message is None:
124            error_message = global_conf.get('error_message')
125        self.error_message = error_message
126        if xmlhttp_key is None:
127            xmlhttp_key = global_conf.get('xmlhttp_key', '_')
128        self.xmlhttp_key = xmlhttp_key
129           
130    def __call__(self, environ, start_response):
131        """
132        The WSGI application interface.
133        """
134        # We want to be careful about not sending headers twice,
135        # and the content type that the app has committed to (if there
136        # is an exception in the iterator body of the response)
137        if environ.get('paste.throw_errors'):
138            return self.application(environ, start_response)
139        environ['paste.throw_errors'] = True
140
141        try:
142            __traceback_supplement__ = Supplement, self, environ
143            app_iter = self.application(environ, start_response)
144            return self.make_catching_iter(app_iter, environ)
145        except:
146            exc_info = sys.exc_info()
147            try:
148                for expect in environ.get('paste.expected_exceptions', []):
149                    if isinstance(exc_info[1], expect):
150                        raise
151                start_response('500 Internal Server Error',
152                               [('content-type', 'text/html')],
153                               exc_info)
154                # @@: it would be nice to deal with bad content types here
155                response = self.exception_handler(exc_info, environ)
156                return [response]
157            finally:
158                # clean up locals...
159                exc_info = None
160
161    def make_catching_iter(self, app_iter, environ):
162        if isinstance(app_iter, (list, tuple)):
163            # These don't raise
164            return app_iter
165        return CatchingIter(app_iter, environ, self)
166
167    def exception_handler(self, exc_info, environ):
168        simple_html_error = False
169        if self.xmlhttp_key:
170            get_vars = wsgilib.parse_querystring(environ)
171            if dict(get_vars).get(self.xmlhttp_key):
172                simple_html_error = True
173        return handle_exception(
174            exc_info, environ['wsgi.errors'],
175            html=True,
176            debug_mode=self.debug_mode,
177            error_email=self.error_email,
178            error_log=self.error_log,
179            show_exceptions_in_wsgi_errors=self.show_exceptions_in_wsgi_errors,
180            error_email_from=self.from_address,
181            smtp_server=self.smtp_server,
182            smtp_username=self.smtp_username,
183            smtp_password=self.smtp_password,
184            smtp_use_tls=self.smtp_use_tls,
185            error_subject_prefix=self.error_subject_prefix,
186            error_message=self.error_message,
187            simple_html_error=simple_html_error)
188
189class CatchingIter(object):
190
191    """
192    A wrapper around the application iterator that will catch
193    exceptions raised by the a generator, or by the close method, and
194    display or report as necessary.
195    """
196
197    def __init__(self, app_iter, environ, error_middleware):
198        self.app_iterable = app_iter
199        self.app_iterator = iter(app_iter)
200        self.environ = environ
201        self.error_middleware = error_middleware
202        self.closed = False
203
204    def __iter__(self):
205        return self
206
207    def next(self):
208        __traceback_supplement__ = (
209            Supplement, self.error_middleware, self.environ)
210        if self.closed:
211            raise StopIteration
212        try:
213            return self.app_iterator.next()
214        except StopIteration:
215            self.closed = True
216            close_response = self._close()
217            if close_response is not None:
218                return close_response
219            else:
220                raise StopIteration
221        except:
222            self.closed = True
223            close_response = self._close()
224            response = self.error_middleware.exception_handler(
225                sys.exc_info(), self.environ)
226            if close_response is not None:
227                response += (
228                    '<hr noshade>Error in .close():<br>%s'
229                    % close_response)
230            return response
231
232    def close(self):
233        # This should at least print something to stderr if the
234        # close method fails at this point
235        if not self.closed:
236            self._close()
237
238    def _close(self):
239        """Close and return any error message"""
240        if not hasattr(self.app_iterable, 'close'):
241            return None
242        try:
243            self.app_iterable.close()
244            return None
245        except:
246            close_response = self.error_middleware.exception_handler(
247                sys.exc_info(), self.environ)
248            return close_response
249
250
251class Supplement(object):
252
253    """
254    This is a supplement used to display standard WSGI information in
255    the traceback.
256    """
257
258    def __init__(self, middleware, environ):
259        self.middleware = middleware
260        self.environ = environ
261        self.source_url = request.construct_url(environ)
262
263    def extraData(self):
264        data = {}
265        cgi_vars = data[('extra', 'CGI Variables')] = {}
266        wsgi_vars = data[('extra', 'WSGI Variables')] = {}
267        hide_vars = ['paste.config', 'wsgi.errors', 'wsgi.input',
268                     'wsgi.multithread', 'wsgi.multiprocess',
269                     'wsgi.run_once', 'wsgi.version',
270                     'wsgi.url_scheme']
271        for name, value in self.environ.items():
272            if name.upper() == name:
273                if value:
274                    cgi_vars[name] = value
275            elif name not in hide_vars:
276                wsgi_vars[name] = value
277        if self.environ['wsgi.version'] != (1, 0):
278            wsgi_vars['wsgi.version'] = self.environ['wsgi.version']
279        proc_desc = tuple([int(bool(self.environ[key]))
280                           for key in ('wsgi.multiprocess',
281                                       'wsgi.multithread',
282                                       'wsgi.run_once')])
283        wsgi_vars['wsgi process'] = self.process_combos[proc_desc]
284        wsgi_vars['application'] = self.middleware.application
285        if 'paste.config' in self.environ:
286            data[('extra', 'Configuration')] = dict(self.environ['paste.config'])
287        return data
288
289    process_combos = {
290        # multiprocess, multithread, run_once
291        (0, 0, 0): 'Non-concurrent server',
292        (0, 1, 0): 'Multithreaded',
293        (1, 0, 0): 'Multiprocess',
294        (1, 1, 0): 'Multi process AND threads (?)',
295        (0, 0, 1): 'Non-concurrent CGI',
296        (0, 1, 1): 'Multithread CGI (?)',
297        (1, 0, 1): 'CGI',
298        (1, 1, 1): 'Multi thread/process CGI (?)',
299        }
300   
301def handle_exception(exc_info, error_stream, html=True,
302                     debug_mode=False,
303                     error_email=None,
304                     error_log=None,
305                     show_exceptions_in_wsgi_errors=False,
306                     error_email_from='errors@localhost',
307                     smtp_server='localhost',
308                     smtp_username=None,
309                     smtp_password=None,
310                     smtp_use_tls=False,
311                     error_subject_prefix='',
312                     error_message=None,
313                     simple_html_error=False,
314                     ):
315    """
316    For exception handling outside of a web context
317
318    Use like::
319
320        import sys
321        import paste
322        import paste.error_middleware
323        try:
324            do stuff
325        except:
326            paste.error_middleware.exception_handler(
327                sys.exc_info(), paste.CONFIG, sys.stderr, html=False)
328
329    If you want to report, but not fully catch the exception, call
330    ``raise`` after ``exception_handler``, which (when given no argument)
331    will reraise the exception.
332    """
333    reported = False
334    exc_data = collector.collect_exception(*exc_info)
335    extra_data = ''
336    if error_email:
337        rep = reporter.EmailReporter(
338            to_addresses=error_email,
339            from_address=error_email_from,
340            smtp_server=smtp_server,
341            smtp_username=smtp_username,
342            smtp_password=smtp_password,
343            smtp_use_tls=smtp_use_tls,
344            subject_prefix=error_subject_prefix)
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    if error_log:
351        rep = reporter.LogReporter(
352            filename=error_log)
353        rep_err = send_report(rep, exc_data, html=html)
354        if rep_err:
355            extra_data += rep_err
356        else:
357            reported = True
358    if show_exceptions_in_wsgi_errors:
359        rep = reporter.FileReporter(
360            file=error_stream)
361        rep_err = send_report(rep, exc_data, html=html)
362        if rep_err:
363            extra_data += rep_err
364        else:
365            reported = True
366    else:
367        error_stream.write('Error - %s: %s\n' % (
368            exc_data.exception_type, exc_data.exception_value))
369    if html:
370        if debug_mode and simple_html_error:
371            return_error = formatter.format_html(
372                exc_data, include_hidden_frames=False,
373                include_reusable=False, show_extra_data=False)
374            reported = True
375        elif debug_mode and not simple_html_error:
376            error_html = formatter.format_html(
377                exc_data,
378                include_hidden_frames=True,
379                include_reusable=False)
380            head_html = formatter.error_css + formatter.hide_display_js
381            return_error = error_template(
382                head_html, error_html, extra_data)
383            extra_data = ''
384            reported = True
385        else:
386            msg = error_message or '''
387            An error occurred.  See the error logs for more information.
388            (Turn debug on to display exception reports here)
389            '''
390            return_error = error_template('', msg, '')
391    else:
392        return_error = None
393    if not reported and error_stream:
394        err_report = formatter.format_text(exc_data, show_hidden_frames=True)
395        err_report += '\n' + '-'*60 + '\n'
396        error_stream.write(err_report)
397    if extra_data:
398        error_stream.write(extra_data)
399    return return_error
400
401def send_report(rep, exc_data, html=True):
402    try:
403        rep.report(exc_data)
404    except:
405        output = StringIO()
406        traceback.print_exc(file=output)
407        if html:
408            return """
409            <p>Additionally an error occurred while sending the %s report:
410
411            <pre>%s</pre>
412            </p>""" % (
413                cgi.escape(str(rep)), output.getvalue())
414        else:
415            return (
416                "Additionally an error occurred while sending the "
417                "%s report:\n%s" % (str(rep), output.getvalue()))
418    else:
419        return ''
420
421def error_template(head_html, exception, extra):
422    return '''
423    <html>
424    <head>
425    <title>Server Error</title>
426    %s
427    </head>
428    <body>
429    <h1>Server Error</h1>
430    %s
431    %s
432    </body>
433    </html>''' % (head_html, exception, extra)
434
435def make_error_middleware(app, global_conf, **kw):
436    return ErrorMiddleware(app, global_conf=global_conf, **kw)
437
438doc_lines = ErrorMiddleware.__doc__.splitlines(True)
439for i in range(len(doc_lines)):
440    if doc_lines[i].strip().startswith('Settings'):
441        make_error_middleware.__doc__ = ''.join(doc_lines[i:])
442        break
443del i, doc_lines
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。