mirror of https://github.com/pallets/flask.git
Added logging support.
This commit is contained in:
parent
8c26bec55c
commit
e7f67e1333
4
CHANGES
4
CHANGES
|
@ -9,6 +9,10 @@ Version 0.5
|
|||
Release date to be announced
|
||||
|
||||
- added support for categories for flashed messages.
|
||||
- the application now configures a :class:`logging.Handler` and will
|
||||
log request handling exceptions to that logger when not in debug
|
||||
mode. This makes it possible to receive mails on server errors
|
||||
for example.
|
||||
|
||||
Version 0.2
|
||||
-----------
|
||||
|
|
74
flask.py
74
flask.py
|
@ -21,7 +21,7 @@ from werkzeug import Request as RequestBase, Response as ResponseBase, \
|
|||
LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \
|
||||
ImmutableDict, cached_property, wrap_file, Headers
|
||||
from werkzeug.routing import Map, Rule
|
||||
from werkzeug.exceptions import HTTPException
|
||||
from werkzeug.exceptions import HTTPException, InternalServerError
|
||||
from werkzeug.contrib.securecookie import SecureCookie
|
||||
|
||||
# try to load the best simplejson implementation available. If JSON
|
||||
|
@ -659,6 +659,18 @@ class Flask(_PackageBoundObject):
|
|||
#: .. versionadded:: 0.2
|
||||
use_x_sendfile = False
|
||||
|
||||
#: the logging format used for the debug logger. This is only used when
|
||||
#: the application is in debug mode, otherwise the attached logging
|
||||
#: handler does the formatting.
|
||||
#:
|
||||
#: .. versionadded:: 0.5
|
||||
debug_log_format = (
|
||||
'-' * 80 + '\n' +
|
||||
'%(levelname)s in %(module)s, %(filename)s:%(lineno)d]:\n' +
|
||||
'%(message)s\n' +
|
||||
'-' * 80
|
||||
)
|
||||
|
||||
#: options that are passed directly to the Jinja2 environment
|
||||
jinja_options = ImmutableDict(
|
||||
autoescape=True,
|
||||
|
@ -753,6 +765,24 @@ class Flask(_PackageBoundObject):
|
|||
)
|
||||
self.jinja_env.filters['tojson'] = _tojson_filter
|
||||
|
||||
@cached_property
|
||||
def logger(self):
|
||||
"""A :class:`logging.Logger` object for this application. The
|
||||
default configuration is to log to stderr if the application is
|
||||
in debug mode.
|
||||
"""
|
||||
from logging import getLogger, StreamHandler, Formatter, DEBUG
|
||||
class DebugHandler(StreamHandler):
|
||||
def emit(x, record):
|
||||
if self.debug:
|
||||
StreamHandler.emit(x, record)
|
||||
handler = DebugHandler()
|
||||
handler.setLevel(DEBUG)
|
||||
handler.setFormatter(Formatter(self.debug_log_format))
|
||||
logger = getLogger(self.import_name)
|
||||
logger.addHandler(handler)
|
||||
return logger
|
||||
|
||||
def create_jinja_loader(self):
|
||||
"""Creates the Jinja loader. By default just a package loader for
|
||||
the configured package is returned that looks up templates in the
|
||||
|
@ -1010,6 +1040,38 @@ class Flask(_PackageBoundObject):
|
|||
self.template_context_processors[None].append(f)
|
||||
return f
|
||||
|
||||
def handle_http_exception(self, e):
|
||||
"""Handles an HTTP exception. By default this will invoke the
|
||||
registered error handlers and fall back to returning the
|
||||
exception as response.
|
||||
|
||||
.. versionadded: 0.5
|
||||
"""
|
||||
handler = self.error_handlers.get(e.code)
|
||||
if handler is None:
|
||||
return e
|
||||
return handler(e)
|
||||
|
||||
def handle_exception(self, e):
|
||||
"""Default exception handling that kicks in when an exception
|
||||
occours that is not catched. In debug mode the exception will
|
||||
be re-raised immediately, otherwise it is logged an the handler
|
||||
for an 500 internal server error is used. If no such handler
|
||||
exists, a default 500 internal server error message is displayed.
|
||||
|
||||
.. versionadded: 0.5
|
||||
"""
|
||||
handler = self.error_handlers.get(500)
|
||||
if self.debug:
|
||||
raise
|
||||
self.logger.exception('Exception on %s [%s]' % (
|
||||
request.path,
|
||||
request.method
|
||||
))
|
||||
if handler is None:
|
||||
return InternalServerError()
|
||||
return handler(e)
|
||||
|
||||
def dispatch_request(self):
|
||||
"""Does the request dispatching. Matches the URL and returns the
|
||||
return value of the view or error handler. This does not have to
|
||||
|
@ -1022,15 +1084,9 @@ class Flask(_PackageBoundObject):
|
|||
raise req.routing_exception
|
||||
return self.view_functions[req.endpoint](**req.view_args)
|
||||
except HTTPException, e:
|
||||
handler = self.error_handlers.get(e.code)
|
||||
if handler is None:
|
||||
return e
|
||||
return handler(e)
|
||||
return self.handle_http_exception(e)
|
||||
except Exception, e:
|
||||
handler = self.error_handlers.get(500)
|
||||
if self.debug or handler is None:
|
||||
raise
|
||||
return handler(e)
|
||||
return self.handle_exception(e)
|
||||
|
||||
def make_response(self, rv):
|
||||
"""Converts the return value from a view function to a real
|
||||
|
|
|
@ -16,6 +16,7 @@ import sys
|
|||
import flask
|
||||
import unittest
|
||||
import tempfile
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from werkzeug import parse_date, parse_options_header
|
||||
from cStringIO import StringIO
|
||||
|
@ -26,6 +27,16 @@ sys.path.append(os.path.join(example_path, 'flaskr'))
|
|||
sys.path.append(os.path.join(example_path, 'minitwit'))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def catch_stderr():
|
||||
old_stderr = sys.stderr
|
||||
sys.stderr = rv = StringIO()
|
||||
try:
|
||||
yield rv
|
||||
finally:
|
||||
sys.stderr = old_stderr
|
||||
|
||||
|
||||
class ContextTestCase(unittest.TestCase):
|
||||
|
||||
def test_context_binding(self):
|
||||
|
@ -585,6 +596,56 @@ class SendfileTestCase(unittest.TestCase):
|
|||
assert options['filename'] == 'index.txt'
|
||||
|
||||
|
||||
class LoggingTestCase(unittest.TestCase):
|
||||
|
||||
def test_debug_log(self):
|
||||
app = flask.Flask(__name__)
|
||||
app.debug = True
|
||||
@app.route('/')
|
||||
def index():
|
||||
app.logger.warning('the standard library is dead')
|
||||
return ''
|
||||
|
||||
@app.route('/exc')
|
||||
def exc():
|
||||
1/0
|
||||
c = app.test_client()
|
||||
|
||||
with catch_stderr() as err:
|
||||
rv = c.get('/')
|
||||
out = err.getvalue()
|
||||
assert 'WARNING in flask_tests, flask_tests.py' in out
|
||||
assert 'the standard library is dead' in out
|
||||
|
||||
with catch_stderr() as err:
|
||||
try:
|
||||
c.get('/exc')
|
||||
except ZeroDivisionError:
|
||||
pass
|
||||
else:
|
||||
assert False, 'debug log ate the exception'
|
||||
|
||||
def test_exception_logging(self):
|
||||
from logging import StreamHandler
|
||||
out = StringIO()
|
||||
app = flask.Flask(__name__)
|
||||
app.logger.addHandler(StreamHandler(out))
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
1/0
|
||||
|
||||
rv = app.test_client().get('/')
|
||||
assert rv.status_code == 500
|
||||
assert 'Internal Server Error' in rv.data
|
||||
|
||||
err = out.getvalue()
|
||||
assert 'Exception on / [GET]' in err
|
||||
assert 'Traceback (most recent call last):' in err
|
||||
assert '1/0' in err
|
||||
assert 'ZeroDivisionError:' in err
|
||||
|
||||
|
||||
def suite():
|
||||
from minitwit_tests import MiniTwitTestCase
|
||||
from flaskr_tests import FlaskrTestCase
|
||||
|
@ -592,8 +653,9 @@ def suite():
|
|||
suite.addTest(unittest.makeSuite(ContextTestCase))
|
||||
suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase))
|
||||
suite.addTest(unittest.makeSuite(TemplatingTestCase))
|
||||
suite.addTest(unittest.makeSuite(SendfileTestCase))
|
||||
suite.addTest(unittest.makeSuite(ModuleTestCase))
|
||||
suite.addTest(unittest.makeSuite(SendfileTestCase))
|
||||
suite.addTest(unittest.makeSuite(LoggingTestCase))
|
||||
if flask.json_available:
|
||||
suite.addTest(unittest.makeSuite(JSONTestCase))
|
||||
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
|
||||
|
|
Loading…
Reference in New Issue