summaryrefslogtreecommitdiff
path: root/paste/exceptions/errormiddleware.py
diff options
context:
space:
mode:
Diffstat (limited to 'paste/exceptions/errormiddleware.py')
-rw-r--r--paste/exceptions/errormiddleware.py300
1 files changed, 300 insertions, 0 deletions
diff --git a/paste/exceptions/errormiddleware.py b/paste/exceptions/errormiddleware.py
new file mode 100644
index 0000000..0f4541c
--- /dev/null
+++ b/paste/exceptions/errormiddleware.py
@@ -0,0 +1,300 @@
+"""
+Error handler middleware
+"""
+import sys
+import traceback
+import cgi
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+from paste.exceptions import formatter, collector, reporter
+from paste import wsgilib
+from paste.docsupport import metadata
+import pkg_resources
+from paste.deploy import converters
+
+__all__ = ['ErrorMiddleware', 'handle_exception']
+
+class NoDefault:
+ pass
+
+class ErrorMiddleware(object):
+
+ """
+ Usage::
+
+ error_caching_wsgi_app = ErrorMiddleware(wsgi_app)
+
+ These configuration keys are used:
+
+ ``debug``:
+ show the errors in the browser
+ ``error_email``:
+ if present, send errors to this email address
+ ``error_log``:
+ if present, write errors to this file
+ ``show_exceptions_in_error_log``:
+ if true (the default) then write errors to wsgi.errors
+
+ By setting 'paste.throw_errors' to a true value, this middleware is
+ disabled. This can be useful in a testing environment where you don't
+ want errors to be caught and transformed.
+ """
+
+ def __init__(self, application, global_conf,
+ debug=NoDefault,
+ error_email=None,
+ error_log=None,
+ show_exceptions_in_wsgi_errors=False,
+ from_address=None,
+ smtp_server=None,
+ error_subject_prefix=None,
+ error_message=None):
+ self.application = application
+ if debug is NoDefault:
+ debug = global_conf.get('debug')
+ self.debug_mode = converters.asbool(debug)
+ if error_email is None:
+ error_email = (global_conf.get('error_email')
+ or global_conf.get('admin_email')
+ or global_conf.get('webmaster_email')
+ or global_conf.get('sysadmin_email'))
+ self.error_email = converters.aslist(error_email)
+ self.error_log = error_log
+ self.show_exceptions_in_wsgi_errors = show_exceptions_in_wsgi_errors
+ if from_address is None:
+ from_address = global_conf.get('error_from_address', 'errors@localhost')
+ self.from_address = from_address
+ if smtp_server is None:
+ smtp_server = global_conf.get('smtp_server', 'localhost')
+ self.smtp_server = smtp_server
+ self.error_subject_prefix = error_subject_prefix or ''
+ if error_message is None:
+ error_message = global_conf.get('error_message')
+ self.error_message = error_message
+
+ def __call__(self, environ, start_response):
+ # We want to be careful about not sending headers twice,
+ # and the content type that the app has committed to (if there
+ # is an exception in the iterator body of the response)
+ started = []
+ if environ.get('paste.throw_errors'):
+ return self.application(environ, start_response)
+ environ['paste.throw_errors'] = True
+
+ def detect_start_response(status, headers, exc_info=None):
+ try:
+ return start_response(status, headers, exc_info)
+ except:
+ raise
+ else:
+ started.append(True)
+ try:
+ __traceback_supplement__ = Supplement, self, environ
+ app_iter = self.application(environ, detect_start_response)
+ return self.catching_iter(app_iter, environ)
+ except:
+ exc_info = sys.exc_info()
+ if not started:
+ start_response('500 Internal Server Error',
+ [('content-type', 'text/html')],
+ exc_info)
+ # @@: it would be nice to deal with bad content types here
+ response = self.exception_handler(exc_info, environ)
+ return [response]
+
+ def catching_iter(self, app_iter, environ):
+ __traceback_supplement__ = Supplement, self, environ
+ if not app_iter:
+ raise StopIteration
+ error_on_close = False
+ try:
+ for v in app_iter:
+ yield v
+ if hasattr(app_iter, 'close'):
+ error_on_close = True
+ app_iter.close()
+ except:
+ response = self.exception_handler(sys.exc_info(), environ)
+ if not error_on_close and hasattr(app_iter, 'close'):
+ try:
+ app_iter.close()
+ except:
+ close_response = self.exception_handler(
+ sys.exc_info(), environ)
+ response += (
+ '<hr noshade>Error in .close():<br>%s'
+ % close_response)
+ yield response
+
+ def exception_handler(self, exc_info, environ):
+ return handle_exception(
+ exc_info, environ['wsgi.errors'],
+ html=True,
+ debug_mode=self.debug_mode,
+ error_email=self.error_email,
+ error_log=self.error_log,
+ show_exceptions_in_wsgi_errors=self.show_exceptions_in_wsgi_errors,
+ error_email_from=self.error_email_from,
+ smtp_server=self.smtp_server,
+ error_subject_prefix=self.error_subject_prefix,
+ error_message=self.error_message)
+
+class Supplement(object):
+ def __init__(self, middleware, environ):
+ self.middleware = middleware
+ self.environ = environ
+ self.source_url = wsgilib.construct_url(environ)
+ def extraData(self):
+ data = {}
+ cgi_vars = data[('extra', 'CGI Variables')] = {}
+ wsgi_vars = data[('extra', 'WSGI Variables')] = {}
+ hide_vars = ['paste.config', 'wsgi.errors', 'wsgi.input',
+ 'wsgi.multithread', 'wsgi.multiprocess',
+ 'wsgi.run_once', 'wsgi.version',
+ 'wsgi.url_scheme']
+ for name, value in self.environ.items():
+ if name.upper() == name:
+ if value:
+ cgi_vars[name] = value
+ elif name not in hide_vars:
+ wsgi_vars[name] = value
+ if self.environ['wsgi.version'] != (1, 0):
+ wsgi_vars['wsgi.version'] = self.environ['wsgi.version']
+ proc_desc = tuple([int(bool(self.environ[key]))
+ for key in ('wsgi.multiprocess',
+ 'wsgi.multithread',
+ 'wsgi.run_once')])
+ wsgi_vars['wsgi process'] = self.process_combos[proc_desc]
+ wsgi_vars['application'] = self.middleware.application
+ data[('extra', 'Configuration')] = dict(self.environ['paste.config'])
+ return data
+
+ process_combos = {
+ # multiprocess, multithread, run_once
+ (0, 0, 0): 'Non-concurrent server',
+ (0, 1, 0): 'Multithreaded',
+ (1, 0, 0): 'Multiprocess',
+ (1, 1, 0): 'Multi process AND threads (?)',
+ (0, 0, 1): 'Non-concurrent CGI',
+ (0, 1, 1): 'Multithread CGI (?)',
+ (1, 0, 1): 'CGI',
+ (1, 1, 1): 'Multi thread/process CGI (?)',
+ }
+
+def handle_exception(exc_info, error_stream, html=True,
+ debug_mode=False,
+ error_email=None,
+ error_log=None,
+ show_exceptions_in_wsgi_errors=False
+ error_email_from='errors@localhost',
+ smtp_server='localhost',
+ error_subject_prefix='',
+ ):
+ """
+ You can also use exception handling outside of a web context,
+ like::
+
+ import sys
+ import paste
+ import paste.error_middleware
+ try:
+ do stuff
+ except:
+ paste.error_middleware.exception_handler(
+ sys.exc_info(), paste.CONFIG, sys.stderr, html=False)
+
+ If you want to report, but not fully catch the exception, call
+ ``raise`` after ``exception_handler``, which (when given no argument)
+ will reraise the exception.
+ """
+ reported = False
+ exc_data = collector.collect_exception(*exc_info)
+ extra_data = ''
+ if error_email:
+ rep = reporter.EmailReporter(
+ to_addresses=error_email,
+ from_address=error_email_from,
+ smtp_server=smtp_server,
+ subject_prefix=error_subject_prefix)
+ rep_err = send_report(rep, exc_data, html=html)
+ if rep_err:
+ extra_data += rep_err
+ else:
+ reported = True
+ if error_log:
+ rep = reporter.LogReporter(
+ filename=error_log)
+ rep_err = send_report(rep, exc_data, html=html)
+ if rep_err:
+ extra_data += rep_err
+ else:
+ reported = True
+ if show_exceptions_in_wsgi_errors:
+ rep = reporter.FileReporter(
+ file=error_stream)
+ rep_err = send_report(rep, exc_data, html=html)
+ if rep_err:
+ extra_data += rep_err
+ else:
+ reported = True
+ else:
+ error_stream.write('Error - %s: %s\n' % (
+ exc_data.exception_type, exc_data.exception_value))
+ if html:
+ if debug_mode:
+ error_html = formatter.format_html(exc_data,
+ include_hidden_frames=True)
+ return_error = error_template(
+ error_html, extra_data)
+ extra_data = ''
+ reported = True
+ else:
+ return_error = error_template(
+ error_message or '''
+ An error occurred. See the error logs for more information.
+ (Turn debug on to display exception reports here)
+ ''', '')
+ else:
+ return_error = None
+ if not reported and error_stream:
+ err_report = formatter.format_text(exc_data, show_hidden_frames=True)
+ err_report += '\n' + '-'*60 + '\n'
+ error_stream.write(err_report)
+ if extra_data:
+ error_stream.write(extra_data)
+ return return_error
+
+def send_report(rep, exc_data, html=True):
+ try:
+ rep.report(exc_data)
+ except:
+ output = StringIO()
+ traceback.print_exc(file=output)
+ if html:
+ return """
+ <p>Additionally an error occurred while sending the %s report:
+
+ <pre>%s</pre>
+ </p>""" % (
+ cgi.escape(str(rep)), output.getvalue())
+ else:
+ return (
+ "Additionally an error occurred while sending the "
+ "%s report:\n%s" % (str(rep), output.getvalue()))
+ else:
+ return ''
+
+def error_template(exception, extra):
+ return '''
+ <html>
+ <head>
+ <title>Server Error</title>
+ </head>
+ <body>
+ <h1>Server Error</h1>
+ %s
+ %s
+ </body>
+ </html>''' % (exception, extra)