[1.10.x] Fixed #27271 -- Fixed a crash in runserver logging.

Allowed ServerFormatter to handle simple string messages or messages with
formatting arguments. The formatter will set the server_time variable on
the log record if it's required by the format string but wasn't passed in
extra parameters.

Backport of 6709ea4ae9 from master
This commit is contained in:
Sergey Fursov 2016-09-26 01:25:38 +03:00 committed by Tim Graham
parent 6855dac0f9
commit bcdd13de93
3 changed files with 66 additions and 12 deletions

View File

@ -164,28 +164,32 @@ class ServerFormatter(logging.Formatter):
super(ServerFormatter, self).__init__(*args, **kwargs) super(ServerFormatter, self).__init__(*args, **kwargs)
def format(self, record): def format(self, record):
args = record.args
msg = record.msg msg = record.msg
status_code = getattr(record, 'status_code', None)
if len(args) == 0: if status_code:
msg = self.style.HTTP_BAD_REQUEST(msg) if 200 <= status_code < 300:
else:
if args[1][0] == '2':
# Put 2XX first, since it should be the common case # Put 2XX first, since it should be the common case
msg = self.style.HTTP_SUCCESS(msg) msg = self.style.HTTP_SUCCESS(msg)
elif args[1][0] == '1': elif 100 <= status_code < 200:
msg = self.style.HTTP_INFO(msg) msg = self.style.HTTP_INFO(msg)
elif args[1] == '304': elif status_code == 304:
msg = self.style.HTTP_NOT_MODIFIED(msg) msg = self.style.HTTP_NOT_MODIFIED(msg)
elif args[1][0] == '3': elif 300 <= status_code < 400:
msg = self.style.HTTP_REDIRECT(msg) msg = self.style.HTTP_REDIRECT(msg)
elif args[1] == '404': elif status_code == 404:
msg = self.style.HTTP_NOT_FOUND(msg) msg = self.style.HTTP_NOT_FOUND(msg)
elif args[1][0] == '4': elif 400 <= status_code < 500:
msg = self.style.HTTP_BAD_REQUEST(msg) msg = self.style.HTTP_BAD_REQUEST(msg)
else: else:
# Any 5XX, or any other response # Any 5XX, or any other status code
msg = self.style.HTTP_SERVER_ERROR(msg) msg = self.style.HTTP_SERVER_ERROR(msg)
if self.uses_server_time() and not hasattr(record, 'server_time'):
record.server_time = self.formatTime(record, self.datefmt)
record.msg = msg record.msg = msg
return super(ServerFormatter, self).format(record) return super(ServerFormatter, self).format(record)
def uses_server_time(self):
return self._fmt.find('%(server_time)') >= 0

View File

@ -22,3 +22,6 @@ Bugfixes
``SplitDateTimeWidget``, ``SelectDateWidget``, and ``SplitArrayWidget`` ``SplitDateTimeWidget``, ``SelectDateWidget``, and ``SplitArrayWidget``
(:ticket:`27186`). Custom widgets affected by this issue may need to (:ticket:`27186`). Custom widgets affected by this issue may need to
implement a :meth:`~django.forms.Widget.value_omitted_from_data` method. implement a :meth:`~django.forms.Widget.value_omitted_from_data` method.
* Fixed a crash in ``runserver`` logging during a "Broken pipe" error
(:ticket:`27271`).

View File

@ -3,19 +3,22 @@ from __future__ import unicode_literals
import logging import logging
import warnings import warnings
from contextlib import contextmanager
from admin_scripts.tests import AdminScriptTestCase from admin_scripts.tests import AdminScriptTestCase
from django.conf import settings from django.conf import settings
from django.core import mail from django.core import mail
from django.core.files.temp import NamedTemporaryFile from django.core.files.temp import NamedTemporaryFile
from django.core.management import color
from django.db import connection from django.db import connection
from django.test import RequestFactory, SimpleTestCase, override_settings from django.test import RequestFactory, SimpleTestCase, override_settings
from django.test.utils import LoggingCaptureMixin, patch_logger from django.test.utils import LoggingCaptureMixin, patch_logger
from django.utils import six
from django.utils.deprecation import RemovedInNextVersionWarning from django.utils.deprecation import RemovedInNextVersionWarning
from django.utils.log import ( from django.utils.log import (
DEFAULT_LOGGING, AdminEmailHandler, CallbackFilter, RequireDebugFalse, DEFAULT_LOGGING, AdminEmailHandler, CallbackFilter, RequireDebugFalse,
RequireDebugTrue, RequireDebugTrue, ServerFormatter,
) )
from .logconfig import MyEmailBackend from .logconfig import MyEmailBackend
@ -513,3 +516,47 @@ class SchemaLoggerTests(SimpleTestCase):
}}, }},
)] )]
) )
class LogFormattersTests(SimpleTestCase):
def test_server_formatter_styles(self):
color_style = color.make_style('')
formatter = ServerFormatter()
formatter.style = color_style
log_msg = 'log message'
status_code_styles = [
(200, 'HTTP_SUCCESS'),
(100, 'HTTP_INFO'),
(304, 'HTTP_NOT_MODIFIED'),
(300, 'HTTP_REDIRECT'),
(404, 'HTTP_NOT_FOUND'),
(400, 'HTTP_BAD_REQUEST'),
(500, 'HTTP_SERVER_ERROR'),
]
for status_code, style in status_code_styles:
record = logging.makeLogRecord({'msg': log_msg, 'status_code': status_code})
self.assertEqual(formatter.format(record), getattr(color_style, style)(log_msg))
record = logging.makeLogRecord({'msg': log_msg})
self.assertEqual(formatter.format(record), log_msg)
def test_server_formatter_default_format(self):
server_time = '2016-09-25 10:20:30'
log_msg = 'log message'
logger = logging.getLogger('django.server')
@contextmanager
def patch_django_server_logger():
old_stream = logger.handlers[0].stream
new_stream = six.StringIO()
logger.handlers[0].stream = new_stream
yield new_stream
logger.handlers[0].stream = old_stream
with patch_django_server_logger() as logger_output:
logger.info(log_msg, extra={'server_time': server_time})
self.assertEqual('[%s] %s\n' % (server_time, log_msg), logger_output.getvalue())
with patch_django_server_logger() as logger_output:
logger.info(log_msg)
six.assertRegex(self, logger_output.getvalue(), r'^\[[-:,.\s\d]+\] %s' % log_msg)