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
|
Release date to be announced
|
||||||
|
|
||||||
- added support for categories for flashed messages.
|
- 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
|
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, \
|
LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \
|
||||||
ImmutableDict, cached_property, wrap_file, Headers
|
ImmutableDict, cached_property, wrap_file, Headers
|
||||||
from werkzeug.routing import Map, Rule
|
from werkzeug.routing import Map, Rule
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException, InternalServerError
|
||||||
from werkzeug.contrib.securecookie import SecureCookie
|
from werkzeug.contrib.securecookie import SecureCookie
|
||||||
|
|
||||||
# try to load the best simplejson implementation available. If JSON
|
# try to load the best simplejson implementation available. If JSON
|
||||||
|
@ -659,6 +659,18 @@ class Flask(_PackageBoundObject):
|
||||||
#: .. versionadded:: 0.2
|
#: .. versionadded:: 0.2
|
||||||
use_x_sendfile = False
|
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
|
#: options that are passed directly to the Jinja2 environment
|
||||||
jinja_options = ImmutableDict(
|
jinja_options = ImmutableDict(
|
||||||
autoescape=True,
|
autoescape=True,
|
||||||
|
@ -753,6 +765,24 @@ class Flask(_PackageBoundObject):
|
||||||
)
|
)
|
||||||
self.jinja_env.filters['tojson'] = _tojson_filter
|
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):
|
def create_jinja_loader(self):
|
||||||
"""Creates the Jinja loader. By default just a package loader for
|
"""Creates the Jinja loader. By default just a package loader for
|
||||||
the configured package is returned that looks up templates in the
|
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)
|
self.template_context_processors[None].append(f)
|
||||||
return 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):
|
def dispatch_request(self):
|
||||||
"""Does the request dispatching. Matches the URL and returns the
|
"""Does the request dispatching. Matches the URL and returns the
|
||||||
return value of the view or error handler. This does not have to
|
return value of the view or error handler. This does not have to
|
||||||
|
@ -1022,15 +1084,9 @@ class Flask(_PackageBoundObject):
|
||||||
raise req.routing_exception
|
raise req.routing_exception
|
||||||
return self.view_functions[req.endpoint](**req.view_args)
|
return self.view_functions[req.endpoint](**req.view_args)
|
||||||
except HTTPException, e:
|
except HTTPException, e:
|
||||||
handler = self.error_handlers.get(e.code)
|
return self.handle_http_exception(e)
|
||||||
if handler is None:
|
|
||||||
return e
|
|
||||||
return handler(e)
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
handler = self.error_handlers.get(500)
|
return self.handle_exception(e)
|
||||||
if self.debug or handler is None:
|
|
||||||
raise
|
|
||||||
return handler(e)
|
|
||||||
|
|
||||||
def make_response(self, rv):
|
def make_response(self, rv):
|
||||||
"""Converts the return value from a view function to a real
|
"""Converts the return value from a view function to a real
|
||||||
|
|
|
@ -16,6 +16,7 @@ import sys
|
||||||
import flask
|
import flask
|
||||||
import unittest
|
import unittest
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from contextlib import contextmanager
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from werkzeug import parse_date, parse_options_header
|
from werkzeug import parse_date, parse_options_header
|
||||||
from cStringIO import StringIO
|
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'))
|
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):
|
class ContextTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def test_context_binding(self):
|
def test_context_binding(self):
|
||||||
|
@ -585,6 +596,56 @@ class SendfileTestCase(unittest.TestCase):
|
||||||
assert options['filename'] == 'index.txt'
|
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():
|
def suite():
|
||||||
from minitwit_tests import MiniTwitTestCase
|
from minitwit_tests import MiniTwitTestCase
|
||||||
from flaskr_tests import FlaskrTestCase
|
from flaskr_tests import FlaskrTestCase
|
||||||
|
@ -592,8 +653,9 @@ def suite():
|
||||||
suite.addTest(unittest.makeSuite(ContextTestCase))
|
suite.addTest(unittest.makeSuite(ContextTestCase))
|
||||||
suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase))
|
suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase))
|
||||||
suite.addTest(unittest.makeSuite(TemplatingTestCase))
|
suite.addTest(unittest.makeSuite(TemplatingTestCase))
|
||||||
suite.addTest(unittest.makeSuite(SendfileTestCase))
|
|
||||||
suite.addTest(unittest.makeSuite(ModuleTestCase))
|
suite.addTest(unittest.makeSuite(ModuleTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(SendfileTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(LoggingTestCase))
|
||||||
if flask.json_available:
|
if flask.json_available:
|
||||||
suite.addTest(unittest.makeSuite(JSONTestCase))
|
suite.addTest(unittest.makeSuite(JSONTestCase))
|
||||||
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
|
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
|
||||||
|
|
Loading…
Reference in New Issue