Browse Source

Added logging support.

pull/1638/head
Armin Ronacher 15 years ago
parent
commit
e7f67e1333
  1. 4
      CHANGES
  2. 74
      flask.py
  3. 64
      tests/flask_tests.py

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

@ -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

64
tests/flask_tests.py

@ -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…
Cancel
Save