Fixed #26688 -- Fixed HTTP request logging inconsistencies.

* Added logging of 500 responses for instantiated responses.
* Added logging of all 4xx and 5xx responses.
This commit is contained in:
Samir Shah 2017-07-13 07:09:18 +03:00 committed by Tim Graham
parent 2e1f674897
commit 10b44e4525
10 changed files with 226 additions and 57 deletions

View file

@ -6,15 +6,18 @@ from admin_scripts.tests import AdminScriptTestCase
from django.conf import settings
from django.core import mail
from django.core.exceptions import PermissionDenied
from django.core.files.temp import NamedTemporaryFile
from django.core.management import color
from django.http.multipartparser import MultiPartParserError
from django.test import RequestFactory, SimpleTestCase, override_settings
from django.test.utils import LoggingCaptureMixin, patch_logger
from django.test.utils import LoggingCaptureMixin
from django.utils.log import (
DEFAULT_LOGGING, AdminEmailHandler, CallbackFilter, RequireDebugFalse,
RequireDebugTrue, ServerFormatter,
)
from . import views
from .logconfig import MyEmailBackend
# logging config prior to using filter with mail_admins
@ -106,16 +109,95 @@ class DefaultLoggingTests(SetupDefaultLoggingMixin, LoggingCaptureMixin, SimpleT
self.assertEqual(self.logger_output.getvalue(), '')
class LoggingAssertionMixin(object):
def assertLogsRequest(self, url, level, msg, status_code, logger='django.request', exc_class=None):
with self.assertLogs(logger, level) as cm:
try:
self.client.get(url)
except views.UncaughtException:
pass
self.assertEqual(
len(cm.records), 1,
"Wrong number of calls for logger %r in %r level." % (logger, level)
)
record = cm.records[0]
self.assertEqual(record.getMessage(), msg)
self.assertEqual(record.status_code, status_code)
if exc_class:
self.assertIsNotNone(record.exc_info)
self.assertEqual(record.exc_info[0], exc_class)
@override_settings(DEBUG=True, ROOT_URLCONF='logging_tests.urls')
class HandlerLoggingTests(SetupDefaultLoggingMixin, LoggingCaptureMixin, SimpleTestCase):
class HandlerLoggingTests(SetupDefaultLoggingMixin, LoggingAssertionMixin, LoggingCaptureMixin, SimpleTestCase):
def test_page_found_no_warning(self):
self.client.get('/innocent/')
self.assertEqual(self.logger_output.getvalue(), '')
def test_redirect_no_warning(self):
self.client.get('/redirect/')
self.assertEqual(self.logger_output.getvalue(), '')
def test_page_not_found_warning(self):
self.client.get('/does_not_exist/')
self.assertEqual(self.logger_output.getvalue(), 'Not Found: /does_not_exist/\n')
self.assertLogsRequest(
url='/does_not_exist/',
level='WARNING',
status_code=404,
msg='Not Found: /does_not_exist/',
)
def test_page_not_found_raised(self):
self.assertLogsRequest(
url='/does_not_exist_raised/',
level='WARNING',
status_code=404,
msg='Not Found: /does_not_exist_raised/',
)
def test_uncaught_exception(self):
self.assertLogsRequest(
url='/uncaught_exception/',
level='ERROR',
status_code=500,
msg='Internal Server Error: /uncaught_exception/',
exc_class=views.UncaughtException,
)
def test_internal_server_error(self):
self.assertLogsRequest(
url='/internal_server_error/',
level='ERROR',
status_code=500,
msg='Internal Server Error: /internal_server_error/',
)
def test_internal_server_error_599(self):
self.assertLogsRequest(
url='/internal_server_error/?status=599',
level='ERROR',
status_code=599,
msg='Unknown Status Code: /internal_server_error/',
)
def test_permission_denied(self):
self.assertLogsRequest(
url='/permission_denied/',
level='WARNING',
status_code=403,
msg='Forbidden (Permission denied): /permission_denied/',
exc_class=PermissionDenied,
)
def test_multi_part_parser_error(self):
self.assertLogsRequest(
url='/multi_part_parser_error/',
level='WARNING',
status_code=400,
msg='Bad request (Unable to parse request body): /multi_part_parser_error/',
exc_class=MultiPartParserError,
)
@override_settings(
@ -401,19 +483,25 @@ class SetupConfigureLogging(SimpleTestCase):
@override_settings(DEBUG=True, ROOT_URLCONF='logging_tests.urls')
class SecurityLoggerTest(SimpleTestCase):
class SecurityLoggerTest(LoggingAssertionMixin, SimpleTestCase):
def test_suspicious_operation_creates_log_message(self):
with patch_logger('django.security.SuspiciousOperation', 'error') as calls:
self.client.get('/suspicious/')
self.assertEqual(len(calls), 1)
self.assertEqual(calls[0], 'dubious')
self.assertLogsRequest(
url='/suspicious/',
level='ERROR',
msg='dubious',
status_code=400,
logger='django.security.SuspiciousOperation',
)
def test_suspicious_operation_uses_sublogger(self):
with patch_logger('django.security.DisallowedHost', 'error') as calls:
self.client.get('/suspicious_spec/')
self.assertEqual(len(calls), 1)
self.assertEqual(calls[0], 'dubious')
self.assertLogsRequest(
url='/suspicious_spec/',
level='ERROR',
msg='dubious',
status_code=400,
logger='django.security.DisallowedHost',
)
@override_settings(
ADMINS=[('admin', 'admin@example.com')],

View file

@ -1,9 +1,16 @@
from django.conf.urls import url
from django.urls import path
from . import views
urlpatterns = [
url(r'^innocent/$', views.innocent),
path('redirect/', views.redirect),
url(r'^suspicious/$', views.suspicious),
url(r'^suspicious_spec/$', views.suspicious_spec),
path('internal_server_error/', views.internal_server_error),
path('uncaught_exception/', views.uncaught_exception),
path('permission_denied/', views.permission_denied),
path('multi_part_parser_error/', views.multi_part_parser_error),
path('does_not_exist_raised/', views.does_not_exist_raised),
]

View file

@ -1,14 +1,48 @@
from django.core.exceptions import DisallowedHost, SuspiciousOperation
from django.http import HttpResponse
from django.core.exceptions import (
DisallowedHost, PermissionDenied, SuspiciousOperation,
)
from django.http import (
Http404, HttpResponse, HttpResponseRedirect, HttpResponseServerError,
)
from django.http.multipartparser import MultiPartParserError
def innocent(request):
return HttpResponse('innocent')
def redirect(request):
return HttpResponseRedirect('/')
def suspicious(request):
raise SuspiciousOperation('dubious')
def suspicious_spec(request):
raise DisallowedHost('dubious')
class UncaughtException(Exception):
pass
def uncaught_exception(request):
raise UncaughtException('Uncaught exception')
def internal_server_error(request):
status = request.GET.get('status', 500)
return HttpResponseServerError('Server Error', status=int(status))
def permission_denied(request):
raise PermissionDenied()
def multi_part_parser_error(request):
raise MultiPartParserError('parsing error')
def does_not_exist_raised(request):
raise Http404('Not Found')