Browse Source

Fixed after_request handlers being called twice in some cases and improved logging system

pull/124/head
Armin Ronacher 15 years ago
parent
commit
9983e84742
  1. 2
      CHANGES
  2. 57
      flask.py
  3. 16
      tests/flask_tests.py

2
CHANGES

@ -16,6 +16,8 @@ Release date to be announced, codename to be selected.
- test client has not the ability to preserve the request context
for a little longer. This can also be used to trigger custom
requests that do not pop the request stack for testing.
- because the Python standard library caches loggers, the name of
the logger is configurable now to better support unittests.
Version 0.3.1
-------------

57
flask.py

@ -16,6 +16,7 @@ import mimetypes
from datetime import datetime, timedelta
from itertools import chain
from threading import Lock
from jinja2 import Environment, PackageLoader, FileSystemLoader
from werkzeug import Request as RequestBase, Response as ResponseBase, \
LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \
@ -50,6 +51,9 @@ try:
except (ImportError, AttributeError):
pkg_resources = None
# a lock used for logger initialization
_logger_lock = Lock()
class Request(RequestBase):
"""The request object used by default in flask. Remembers the
@ -839,6 +843,12 @@ class Flask(_PackageBoundObject):
#: `USE_X_SENDFILE` configuration key. Defaults to `False`.
use_x_sendfile = ConfigAttribute('USE_X_SENDFILE')
#: the name of the logger to use. By default the logger name is the
#: package name passed to the constructor.
#:
#: .. versionadded:: 0.4
logger_name = ConfigAttribute('LOGGER_NAME')
#: 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.
@ -863,7 +873,8 @@ class Flask(_PackageBoundObject):
'SECRET_KEY': None,
'SESSION_COOKIE_NAME': 'session',
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False
'USE_X_SENDFILE': False,
'LOGGER_NAME': None
})
def __init__(self, import_name):
@ -874,6 +885,10 @@ class Flask(_PackageBoundObject):
#: to load a config from files.
self.config = Config(self.root_path, self.default_config)
#: prepare the deferred setup of the logger
self._logger = None
self.logger_name = self.import_name
#: a dictionary of all view functions registered. The keys will
#: be function names which are also used to generate URLs and
#: the values are the function objects themselves.
@ -952,7 +967,7 @@ class Flask(_PackageBoundObject):
)
self.jinja_env.filters['tojson'] = _tojson_filter
@cached_property
@property
def logger(self):
"""A :class:`logging.Logger` object for this application. The
default configuration is to log to stderr if the application is
@ -965,17 +980,23 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.3
"""
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
if self._logger and self._logger.name == self.logger_name:
return self._logger
with _logger_lock:
if self._logger and self._logger.name == self.logger_name:
return self._logger
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.logger_name)
logger.addHandler(handler)
self._logger = logger
return logger
def create_jinja_loader(self):
"""Creates the Jinja loader. By default just a package loader for
@ -1412,16 +1433,12 @@ class Flask(_PackageBoundObject):
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
except Exception, e:
response = self.make_response(self.handle_exception(e))
try:
response = self.process_response(response)
except Exception, e:
response = self.make_response(self.handle_exception(e))
try:
response = self.process_response(response)
except Exception, e:
self.logger.exception('after_request handler failed '
'to postprocess error response. '
'Depending on uncertain state?')
return response(environ, start_response)
def request_context(self, environ):

16
tests/flask_tests.py

@ -288,11 +288,11 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert len(called) == 1
def test_after_request_handler_error(self):
error_out = StringIO()
called = []
app = flask.Flask(__name__)
app.logger.addHandler(StreamHandler(error_out))
@app.after_request
def after_request(response):
called.append(True)
1/0
return response
@app.route('/')
@ -301,7 +301,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
rv = app.test_client().get('/')
assert rv.status_code == 500
assert 'Internal Server Error' in rv.data
assert 'after_request handler failed' in error_out.getvalue()
assert len(called) == 1
def test_error_handling(self):
app = flask.Flask(__name__)
@ -707,6 +707,14 @@ class SendfileTestCase(unittest.TestCase):
class LoggingTestCase(unittest.TestCase):
def test_logger_cache(self):
app = flask.Flask(__name__)
logger1 = app.logger
assert app.logger is logger1
assert logger1.name == __name__
app.logger_name = __name__ + '/test_logger_cache'
assert app.logger is not logger1
def test_debug_log(self):
app = flask.Flask(__name__)
app.debug = True
@ -736,9 +744,9 @@ class LoggingTestCase(unittest.TestCase):
assert False, 'debug log ate the exception'
def test_exception_logging(self):
from logging import StreamHandler
out = StringIO()
app = flask.Flask(__name__)
app.logger_name = 'flask_tests/test_exception_logging'
app.logger.addHandler(StreamHandler(out))
@app.route('/')

Loading…
Cancel
Save