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 - test client has not the ability to preserve the request context
for a little longer. This can also be used to trigger custom for a little longer. This can also be used to trigger custom
requests that do not pop the request stack for testing. 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 Version 0.3.1
------------- -------------

57
flask.py

@ -16,6 +16,7 @@ import mimetypes
from datetime import datetime, timedelta from datetime import datetime, timedelta
from itertools import chain from itertools import chain
from threading import Lock
from jinja2 import Environment, PackageLoader, FileSystemLoader from jinja2 import Environment, PackageLoader, FileSystemLoader
from werkzeug import Request as RequestBase, Response as ResponseBase, \ from werkzeug import Request as RequestBase, Response as ResponseBase, \
LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \ LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \
@ -50,6 +51,9 @@ try:
except (ImportError, AttributeError): except (ImportError, AttributeError):
pkg_resources = None pkg_resources = None
# a lock used for logger initialization
_logger_lock = Lock()
class Request(RequestBase): class Request(RequestBase):
"""The request object used by default in flask. Remembers the """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` configuration key. Defaults to `False`.
use_x_sendfile = ConfigAttribute('USE_X_SENDFILE') 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 logging format used for the debug logger. This is only used when
#: the application is in debug mode, otherwise the attached logging #: the application is in debug mode, otherwise the attached logging
#: handler does the formatting. #: handler does the formatting.
@ -863,7 +873,8 @@ class Flask(_PackageBoundObject):
'SECRET_KEY': None, 'SECRET_KEY': None,
'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_NAME': 'session',
'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False 'USE_X_SENDFILE': False,
'LOGGER_NAME': None
}) })
def __init__(self, import_name): def __init__(self, import_name):
@ -874,6 +885,10 @@ class Flask(_PackageBoundObject):
#: to load a config from files. #: to load a config from files.
self.config = Config(self.root_path, self.default_config) 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 #: a dictionary of all view functions registered. The keys will
#: be function names which are also used to generate URLs and #: be function names which are also used to generate URLs and
#: the values are the function objects themselves. #: the values are the function objects themselves.
@ -952,7 +967,7 @@ class Flask(_PackageBoundObject):
) )
self.jinja_env.filters['tojson'] = _tojson_filter self.jinja_env.filters['tojson'] = _tojson_filter
@cached_property @property
def logger(self): def logger(self):
"""A :class:`logging.Logger` object for this application. The """A :class:`logging.Logger` object for this application. The
default configuration is to log to stderr if the application is default configuration is to log to stderr if the application is
@ -965,17 +980,23 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.3 .. versionadded:: 0.3
""" """
from logging import getLogger, StreamHandler, Formatter, DEBUG if self._logger and self._logger.name == self.logger_name:
class DebugHandler(StreamHandler): return self._logger
def emit(x, record): with _logger_lock:
if self.debug: if self._logger and self._logger.name == self.logger_name:
StreamHandler.emit(x, record) return self._logger
handler = DebugHandler() from logging import getLogger, StreamHandler, Formatter, DEBUG
handler.setLevel(DEBUG) class DebugHandler(StreamHandler):
handler.setFormatter(Formatter(self.debug_log_format)) def emit(x, record):
logger = getLogger(self.import_name) if self.debug:
logger.addHandler(handler) StreamHandler.emit(x, record)
return logger 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): 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
@ -1412,16 +1433,12 @@ class Flask(_PackageBoundObject):
if rv is None: if rv is None:
rv = self.dispatch_request() rv = self.dispatch_request()
response = self.make_response(rv) response = self.make_response(rv)
except Exception, e:
response = self.make_response(self.handle_exception(e))
try:
response = self.process_response(response) response = self.process_response(response)
except Exception, e: except Exception, e:
response = self.make_response(self.handle_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) return response(environ, start_response)
def request_context(self, environ): def request_context(self, environ):

16
tests/flask_tests.py

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

Loading…
Cancel
Save