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

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

64
tests/flask_tests.py

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