2015-11-18 21:15:37 +00:00
|
|
|
"""
|
|
|
|
HTTP related utility mixins.
|
|
|
|
|
|
|
|
- :class:`LoggingHandler`: adds ``self.logger``
|
|
|
|
- :class:`ErrorLogger`: extends ``send_error`` to log useful information
|
2015-11-19 22:29:07 +00:00
|
|
|
- :class:`ErrorWriter`: implements ``send_error`` to write a useful response
|
2015-11-18 21:15:37 +00:00
|
|
|
|
|
|
|
"""
|
|
|
|
import logging
|
2015-11-19 22:29:07 +00:00
|
|
|
import json
|
2015-11-20 12:25:05 +00:00
|
|
|
import traceback
|
2015-11-18 21:15:37 +00:00
|
|
|
|
|
|
|
from tornado import httputil
|
|
|
|
|
|
|
|
|
2015-11-19 22:29:07 +00:00
|
|
|
def _get_http_reason(status_code):
|
|
|
|
return httputil.responses.get(status_code, 'Unknown')
|
|
|
|
|
|
|
|
|
2015-11-18 21:15:37 +00:00
|
|
|
class LoggingHandler(object):
|
|
|
|
"""
|
|
|
|
Add ``self.logger``.
|
|
|
|
|
|
|
|
Mix this into your inheritance chain to add a ``logger``
|
|
|
|
attribute unless one already exists.
|
|
|
|
|
|
|
|
.. attribute:: logger
|
|
|
|
|
|
|
|
Instance of :class:`logging.Logger` with the same name as
|
|
|
|
the class.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
def initialize(self):
|
|
|
|
super(LoggingHandler, self).initialize()
|
|
|
|
if not hasattr(self, 'logger'):
|
|
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class ErrorLogger(LoggingHandler, object):
|
|
|
|
"""
|
|
|
|
Log a message in ``send_error``.
|
|
|
|
|
|
|
|
Mix this class into your inheritance chain to ensure that
|
|
|
|
errors sent via :meth:`tornado.web.RequestHandler.send_error`
|
|
|
|
and :meth:`tornado.web.RequestHandler.write_error` are written
|
|
|
|
to the log.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
def send_error(self, status_code=500, **kwargs):
|
|
|
|
if kwargs.get('reason', None) is None:
|
2015-11-19 13:02:47 +00:00
|
|
|
# so... ReqehstHandler._handle_request_exception explicitly
|
|
|
|
# discards the exc.reason in the case of web.HTTPError...
|
|
|
|
_, exc, _ = kwargs.get('exc_info', (None, None, None))
|
|
|
|
if getattr(exc, 'reason', None):
|
|
|
|
kwargs['reason'] = exc.reason
|
|
|
|
else:
|
|
|
|
# Oh, and make non-standard HTTP status codes NOT explode!
|
2015-11-19 22:29:07 +00:00
|
|
|
kwargs['reason'] = _get_http_reason(status_code)
|
2015-11-18 21:15:37 +00:00
|
|
|
super(ErrorLogger, self).send_error(status_code, **kwargs)
|
|
|
|
|
2015-11-19 13:02:47 +00:00
|
|
|
def write_error(self, status_code, **kwargs):
|
2015-11-18 21:15:37 +00:00
|
|
|
log_function = self.logger.debug
|
|
|
|
if 400 <= status_code < 500:
|
|
|
|
log_function = self.logger.warning
|
|
|
|
else:
|
|
|
|
log_function = self.logger.error
|
|
|
|
|
|
|
|
# N.B. kwargs[reason] is set up by send_error
|
2015-11-19 13:02:47 +00:00
|
|
|
log_function('%s %s failed with %s: %s', self.request.method,
|
2015-11-19 16:51:34 +00:00
|
|
|
self.request.uri, status_code,
|
|
|
|
kwargs.get('log_message', kwargs['reason']))
|
2015-11-18 21:15:37 +00:00
|
|
|
super(ErrorLogger, self).write_error(status_code, **kwargs)
|
2015-11-19 22:29:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ErrorWriter(object):
|
|
|
|
"""
|
|
|
|
Write error bodies out consistently.
|
|
|
|
|
2015-11-20 19:14:39 +00:00
|
|
|
Mix this class in to your inheritance chain to include error bodies in a
|
|
|
|
machine-readable document format.
|
|
|
|
|
|
|
|
If :class:`~sprockets.mixins.mediatype.ContentMixin` is also in use, it
|
|
|
|
will send the error response with it, otherwise the response is sent as
|
|
|
|
a JSON document.
|
|
|
|
|
|
|
|
The error document has three simple properties:
|
2015-11-19 22:29:07 +00:00
|
|
|
|
|
|
|
**type**
|
|
|
|
This is the type of exception that occurred or ``null``.
|
|
|
|
It is only set when :meth:`.write_error` is invoked with
|
|
|
|
a non-empty ``exc_info`` parameter. In that case, it is
|
|
|
|
set to the name of the first value in the :class:`tuple`;
|
|
|
|
IOW, ``exc_type.__name__``.
|
|
|
|
|
|
|
|
**message**
|
|
|
|
This is a description of the error. If exception info is
|
|
|
|
present, then the stringified exception value is used as
|
|
|
|
the message (e.g., ``str(exc_value)``); otherwise, the HTTP
|
|
|
|
``reason`` will be used. If a custom ``reason`` is not
|
|
|
|
present, then the standard HTTP reason phrase is used. In
|
|
|
|
the final case of a non-standard HTTP status code with
|
|
|
|
neither an exception nor a custom reason, the string ``Unknown``
|
|
|
|
will be used.
|
|
|
|
|
2015-11-20 12:25:05 +00:00
|
|
|
**traceback**
|
|
|
|
If the application is configured to serve tracebacks and the
|
|
|
|
error was caused by an exception (based on ``exc_info`` kwarg),
|
|
|
|
then this is the formatted traceback as an array of strings
|
|
|
|
returned from :func:`traceback.format_exception`. Otherwise,
|
|
|
|
this property is set to ``null``.
|
|
|
|
|
2015-11-19 22:29:07 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def write_error(self, status_code, **kwargs):
|
2015-11-20 12:25:05 +00:00
|
|
|
error_body = {'type': None, 'traceback': None}
|
2015-11-19 22:29:07 +00:00
|
|
|
exc_type, exc_value, _ = kwargs.get('exc_info', (None, None, None))
|
|
|
|
if exc_type and exc_value:
|
|
|
|
error_body['type'] = exc_type.__name__
|
|
|
|
error_body.setdefault('message', str(exc_value))
|
2015-11-20 12:25:05 +00:00
|
|
|
if self.settings.get('serve_traceback', False):
|
|
|
|
error_body['traceback'] = traceback.format_exception(
|
|
|
|
*kwargs['exc_info'])
|
2015-11-19 22:29:07 +00:00
|
|
|
else:
|
2015-11-20 12:25:05 +00:00
|
|
|
reason = kwargs.get('reason', _get_http_reason(status_code))
|
|
|
|
error_body.setdefault('message', reason)
|
2015-11-19 22:29:07 +00:00
|
|
|
|
2015-11-20 19:14:39 +00:00
|
|
|
# If sprockets.mixins.media_type is being used, use it
|
|
|
|
if hasattr(self, 'send_response'):
|
|
|
|
self.send_response(error_body)
|
|
|
|
else:
|
|
|
|
self.set_header('Content-Type', 'application/json; charset=utf-8')
|
|
|
|
self.write(json.dumps(error_body).encode('utf-8'))
|