|
1 | 1 | import logging
|
2 | 2 | from contextlib import contextmanager
|
3 | 3 | from io import StringIO
|
4 |
| -from unittest import mock |
| 4 | +from unittest import TestCase, mock |
5 | 5 |
|
6 | 6 | from admin_scripts.tests import AdminScriptTestCase
|
7 | 7 |
|
|
10 | 10 | from django.core.exceptions import DisallowedHost, PermissionDenied, SuspiciousOperation
|
11 | 11 | from django.core.files.temp import NamedTemporaryFile
|
12 | 12 | from django.core.management import color
|
| 13 | +from django.http import HttpResponse |
13 | 14 | from django.http.multipartparser import MultiPartParserError
|
14 | 15 | from django.test import RequestFactory, SimpleTestCase, override_settings
|
15 | 16 | from django.test.utils import LoggingCaptureMixin
|
|
20 | 21 | RequireDebugFalse,
|
21 | 22 | RequireDebugTrue,
|
22 | 23 | ServerFormatter,
|
| 24 | + log_response, |
23 | 25 | )
|
24 | 26 | from django.views.debug import ExceptionReporter
|
25 | 27 |
|
@@ -665,3 +667,122 @@ def patch_django_server_logger():
|
665 | 667 | self.assertRegex(
|
666 | 668 | logger_output.getvalue(), r"^\[[/:,\w\s\d]+\] %s\n" % log_msg
|
667 | 669 | )
|
| 670 | + |
| 671 | + |
| 672 | +class LogResponseRealLoggerTests(TestCase): |
| 673 | + |
| 674 | + request = RequestFactory().get("/test-path/") |
| 675 | + |
| 676 | + def assertResponseLogged(self, logger_cm, msg, levelno, status_code, request): |
| 677 | + self.assertEqual( |
| 678 | + records_len := len(logger_cm.records), |
| 679 | + 1, |
| 680 | + f"Unexpected number of records for {logger_cm=} in {levelno=} (expected 1, " |
| 681 | + f"got {records_len}).", |
| 682 | + ) |
| 683 | + record = logger_cm.records[0] |
| 684 | + self.assertEqual(record.getMessage(), msg) |
| 685 | + self.assertEqual(record.levelno, levelno) |
| 686 | + self.assertEqual(record.status_code, status_code) |
| 687 | + self.assertEqual(record.request, request) |
| 688 | + |
| 689 | + def test_missing_response_raises_attribute_error(self): |
| 690 | + with self.assertRaises(AttributeError): |
| 691 | + log_response("No response provided", response=None, request=self.request) |
| 692 | + |
| 693 | + def test_missing_request_logs_with_none(self): |
| 694 | + response = HttpResponse(status=403) |
| 695 | + with self.assertLogs("django.request", level="INFO") as cm: |
| 696 | + log_response(msg := "Missing request", response=response, request=None) |
| 697 | + self.assertResponseLogged(cm, msg, logging.WARNING, 403, request=None) |
| 698 | + |
| 699 | + def test_logs_5xx_as_error(self): |
| 700 | + response = HttpResponse(status=508) |
| 701 | + with self.assertLogs("django.request", level="ERROR") as cm: |
| 702 | + log_response( |
| 703 | + msg := "Server error occurred", response=response, request=self.request |
| 704 | + ) |
| 705 | + self.assertResponseLogged(cm, msg, logging.ERROR, 508, self.request) |
| 706 | + |
| 707 | + def test_logs_4xx_as_warning(self): |
| 708 | + response = HttpResponse(status=418) |
| 709 | + with self.assertLogs("django.request", level="WARNING") as cm: |
| 710 | + log_response( |
| 711 | + msg := "This is a teapot!", response=response, request=self.request |
| 712 | + ) |
| 713 | + self.assertResponseLogged(cm, msg, logging.WARNING, 418, self.request) |
| 714 | + |
| 715 | + def test_logs_2xx_as_info(self): |
| 716 | + response = HttpResponse(status=201) |
| 717 | + with self.assertLogs("django.request", level="INFO") as cm: |
| 718 | + log_response(msg := "OK response", response=response, request=self.request) |
| 719 | + self.assertResponseLogged(cm, msg, logging.INFO, 201, self.request) |
| 720 | + |
| 721 | + def test_custom_log_level(self): |
| 722 | + response = HttpResponse(status=403) |
| 723 | + with self.assertLogs("django.request", level="DEBUG") as cm: |
| 724 | + log_response( |
| 725 | + msg := "Debug level log", |
| 726 | + response=response, |
| 727 | + request=self.request, |
| 728 | + level="debug", |
| 729 | + ) |
| 730 | + self.assertResponseLogged(cm, msg, logging.DEBUG, 403, self.request) |
| 731 | + |
| 732 | + def test_logs_only_once_per_response(self): |
| 733 | + response = HttpResponse(status=500) |
| 734 | + with self.assertLogs("django.request", level="ERROR") as cm: |
| 735 | + log_response("First log", response=response, request=self.request) |
| 736 | + log_response("Second log", response=response, request=self.request) |
| 737 | + self.assertResponseLogged(cm, "First log", logging.ERROR, 500, self.request) |
| 738 | + |
| 739 | + def test_exc_info_output(self): |
| 740 | + response = HttpResponse(status=500) |
| 741 | + try: |
| 742 | + raise ValueError("Simulated failure") |
| 743 | + except ValueError as exc: |
| 744 | + with self.assertLogs("django.request", level="ERROR") as cm: |
| 745 | + log_response( |
| 746 | + "With exception", |
| 747 | + response=response, |
| 748 | + request=self.request, |
| 749 | + exception=exc, |
| 750 | + ) |
| 751 | + self.assertResponseLogged( |
| 752 | + cm, "With exception", logging.ERROR, 500, self.request |
| 753 | + ) |
| 754 | + self.assertIn("ValueError", "\n".join(cm.output)) # Stack trace included |
| 755 | + |
| 756 | + def test_format_args_are_applied(self): |
| 757 | + response = HttpResponse(status=500) |
| 758 | + with self.assertLogs("django.request", level="ERROR") as cm: |
| 759 | + log_response( |
| 760 | + "Something went wrong: %s (%d)", |
| 761 | + "DB error", |
| 762 | + 42, |
| 763 | + response=response, |
| 764 | + request=self.request, |
| 765 | + ) |
| 766 | + msg = "Something went wrong: DB error (42)" |
| 767 | + self.assertResponseLogged(cm, msg, logging.ERROR, 500, self.request) |
| 768 | + |
| 769 | + def test_logs_with_custom_logger(self): |
| 770 | + handler = logging.StreamHandler(log_stream := StringIO()) |
| 771 | + handler.setFormatter(logging.Formatter("%(levelname)s:%(name)s:%(message)s")) |
| 772 | + |
| 773 | + custom_logger = logging.getLogger("my.custom.logger") |
| 774 | + custom_logger.setLevel(logging.DEBUG) |
| 775 | + custom_logger.addHandler(handler) |
| 776 | + self.addCleanup(custom_logger.removeHandler, handler) |
| 777 | + |
| 778 | + response = HttpResponse(status=404) |
| 779 | + log_response( |
| 780 | + msg := "Handled by custom logger", |
| 781 | + response=response, |
| 782 | + request=self.request, |
| 783 | + logger=custom_logger, |
| 784 | + ) |
| 785 | + |
| 786 | + self.assertEqual( |
| 787 | + f"WARNING:my.custom.logger:{msg}", log_stream.getvalue().strip() |
| 788 | + ) |
0 commit comments