From 5e7a358def89892d110792ecb6f0ac0d3eac53c1 Mon Sep 17 00:00:00 2001 From: Benjamin Sugden Date: Tue, 27 Aug 2024 15:49:37 +0200 Subject: [PATCH] PB-511: Refactor django request context Rename files and variables for better readability and understanding. --- README.md | 21 +++++++++++++------ .../django_middlewares/__init__.py | 0 .../request_middleware.py | 16 +++++++------- .../filters/django_append_request.py | 14 ++++++------- tests/test_django_request.py | 21 +++++++++++++++++-- ...e.py => test_django_request_middleware.py} | 7 ++++--- 6 files changed, 52 insertions(+), 27 deletions(-) create mode 100644 logging_utilities/django_middlewares/__init__.py rename logging_utilities/{ => django_middlewares}/request_middleware.py (91%) rename tests/{test_request_middleware.py => test_django_request_middleware.py} (90%) diff --git a/README.md b/README.md index 8f156d1..25a6c03 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ All features can be fully configured from the configuration file. - [Django Request Config Example](#django-request-config-example) - [Django request log records](#django-request-log-records) - [Usage \& Configuration](#usage--configuration) - - [Configure the filter `DjangoAppendRequestFilter`](#configure-the-filter-djangoappendrequestfilter) - [Filter out LogRecord attributes based on their types](#filter-out-logrecord-attributes-based-on-their-types) - [Attribute Type Filter Constructor](#attribute-type-filter-constructor) - [Attribute Type Filter Config Example](#attribute-type-filter-config-example) @@ -512,19 +511,29 @@ filters: ## Django request log records -To add context information from the current request to log records. For this the filter `DjangoAppendRequestFilter` must be added as well as the middleware `AddRequestToLogMiddleware`. +To add context information from the current request to each log record. For this the filter `DjangoAppendRequestFilter` must be added as well as the middleware `AddRequestToLogMiddleware`. ### Usage & Configuration -Add `logging_utilities.request_middleware.AddRequestToLogMiddleware` to the `settings.MIDDLEWARE` list. +Add `logging_utilities.django_middlewares.request_middleware.AddRequestToLogMiddleware` to the django `settings.MIDDLEWARE` list -#### Configure the filter `DjangoAppendRequestFilter` +For example: + +```python +MIDDLEWARE = ( + ..., + 'logging_utilities.django_middlewares.request_middleware.AddRequestToLogMiddleware', + ..., +) +``` + +Configure the logging filter `DjangoAppendRequestFilter`: ```yaml filters: django_request_meta: (): logging_utilities.filters.django_append_request.DjangoAppendRequestFilter - request_attributes: + attributes: - path - method - META.QUERY_STRING @@ -533,7 +542,7 @@ filters: | Parameter | Type | Default | Description | |--------------|------|---------|------------------------------------------------| -| `attributes` | list | None | All request attributes that match any of the dotted keys of the list will be added to the jsonifiable object. When `None` then no attributes are added (default behavior). | +| `attributes` | list | None | All request attributes that match any of the dotted keys of this list will be added to the log record. When `None` then no attributes are added (default behavior). | | `always_add` | bool | False | By default empty attributes are omitted. If set, empty attributes will be added with value `-` | diff --git a/logging_utilities/django_middlewares/__init__.py b/logging_utilities/django_middlewares/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/logging_utilities/request_middleware.py b/logging_utilities/django_middlewares/request_middleware.py similarity index 91% rename from logging_utilities/request_middleware.py rename to logging_utilities/django_middlewares/request_middleware.py index 988f7fe..89f5e7a 100644 --- a/logging_utilities/request_middleware.py +++ b/logging_utilities/django_middlewares/request_middleware.py @@ -33,8 +33,8 @@ class AddRequestToLogMiddleware(): """Middleware that adds a logging filter *DjangoAppendRequestFilter* to the request. """ - def __init__(self, get_response: Callable[[WSGIRequest], Any], root: str = ""): - self.root = root + def __init__(self, get_response: Callable[[WSGIRequest], Any], root_logger: str = ""): + self.root_logger = root_logger self.get_response = get_response def __call__(self, request: WSGIRequest) -> Any: @@ -50,13 +50,13 @@ def _find_loggers(self) -> dict[str, logging.Logger]: """Return loggers part of root. """ result: dict[str, logging.Logger] = {} - prefix = self.root + "." + prefix = self.root_logger + "." for name, log in logging.Logger.manager.loggerDict.items(): if not isinstance(log, logging.Logger) or not name.startswith(prefix): - continue # not under self.root + continue # not under self.root_logger result[name] = log # also add root logger - result[self.root] = logging.getLogger(self.root) + result[self.root_logger] = logging.getLogger(self.root_logger) return result def _find_handlers(self) -> list[logging.Handler]: @@ -73,13 +73,13 @@ def _find_handlers_with_filter(self, filter_cls: type) -> dict[logging.Handler, Only include handlers that have at least one filter of type *filter_cls*. """ result = {} - for logger in self._find_handlers(): + for handler in self._find_handlers(): attrs = [] - for f in logger.filters: + for f in handler.filters: if isinstance(f, filter_cls): attrs.extend(f.attributes) if attrs: - result[logger] = attrs + result[handler] = attrs return result def _add_filter(self, f: DjangoAppendRequestFilter) -> None: diff --git a/logging_utilities/filters/django_append_request.py b/logging_utilities/filters/django_append_request.py index 8191ede..498ba99 100644 --- a/logging_utilities/filters/django_append_request.py +++ b/logging_utilities/filters/django_append_request.py @@ -5,7 +5,7 @@ from django.core.handlers.wsgi import WSGIRequest -def r_getattr(obj, attr, *args): +def request_getattr(obj, attr, *args): def _getattr(obj, attr): if isinstance(obj, dict): @@ -21,28 +21,26 @@ class DjangoAppendRequestFilter(): This filter adds Django request context attributes to the log record. """ - def __init__( - self, request: Optional[WSGIRequest] = None, request_attributes=None, always_add=False - ): + def __init__(self, request: Optional[WSGIRequest] = None, attributes=None, always_add=False): """Initialize the filter Args: - request_attributes: (WSGIRequest | None) + request: (WSGIRequest | None) Request from which to read the attributes from. attributes: (list | None) - Request attributes that should be added to log entries + Request attributes that should be added to log entries. always_add: bool Always add attributes even if they are missing. Missing attributes with have the value "-". """ self.request = request - self.attributes = request_attributes if request_attributes else list() + self.attributes = attributes if attributes else list() self.always_add = always_add def filter(self, record: LogRecord) -> bool: request = self.request for attr in self.attributes: - val = r_getattr(request, attr, "-") + val = request_getattr(request, attr, "-") if self.always_add or val != "-": setattr(record, "request." + attr, val) diff --git a/tests/test_django_request.py b/tests/test_django_request.py index d09909c..5514abf 100644 --- a/tests/test_django_request.py +++ b/tests/test_django_request.py @@ -42,7 +42,7 @@ def test_django_request_log(self): self._configure_django_filter( test_logger, DjangoAppendRequestFilter( - request, request_attributes=["path", "method", "META.QUERY_STRING"] + request, attributes=["path", "method", "META.QUERY_STRING"] ) ) @@ -73,7 +73,7 @@ def test_django_request_log_always_add(self): self._configure_django_filter( test_logger, DjangoAppendRequestFilter( - request, request_attributes=["does", "not", "exist"], always_add=True + request, attributes=["does", "not", "exist"], always_add=True ) ) @@ -94,3 +94,20 @@ def test_django_request_log_always_add(self): ("message", "second message"), ("request.does", "-"), ("request.not", "-"), ("request.exist", "-")]) ) + + def test_django_request_log_no_request(self): + with self.assertLogs('test_formatter', level=logging.DEBUG) as ctx: + test_logger = logging.getLogger("test_formatter") + self._configure_django_filter( + test_logger, + DjangoAppendRequestFilter(request=None, attributes=["path"], always_add=True) + ) + + test_logger.debug("first message") + + message1 = json.loads(ctx.output[0], object_pairs_hook=dictionary) + self.assertDictEqual( + message1, + dictionary([("levelname", "DEBUG"), ("name", "test_formatter"), + ("message", "first message"), ("request.path", "-")]) + ) diff --git a/tests/test_request_middleware.py b/tests/test_django_request_middleware.py similarity index 90% rename from tests/test_request_middleware.py rename to tests/test_django_request_middleware.py index 4edaf22..50d8367 100644 --- a/tests/test_request_middleware.py +++ b/tests/test_django_request_middleware.py @@ -7,10 +7,11 @@ from django.conf import settings from django.test import RequestFactory +from logging_utilities.django_middlewares.request_middleware import \ + AddRequestToLogMiddleware from logging_utilities.filters.django_append_request import \ DjangoAppendRequestFilter from logging_utilities.formatters.json_formatter import JsonFormatter -from logging_utilities.request_middleware import AddRequestToLogMiddleware # From python3.7, dict is ordered if sys.version_info.major >= 3 and sys.version_info.minor >= 7: @@ -34,7 +35,7 @@ def _configure_django_filter(cls, _logger): for handler in _logger.handlers: handler.setFormatter(JsonFormatter(add_always_extra=True)) django_filter = DjangoAppendRequestFilter( - request_attributes=["method", "path", "META.QUERY_STRING", "headers"] + attributes=["method", "path", "META.QUERY_STRING", "headers"] ) handler.addFilter(django_filter) @@ -48,7 +49,7 @@ def test_handler(request): self._configure_django_filter(logger) my_header = {"HTTP_CUSTOM_KEY": "VALUE"} request = self.factory.get("/some_path?test=some_value", **my_header) - middleware = AddRequestToLogMiddleware(test_handler, root="tests") + middleware = AddRequestToLogMiddleware(test_handler, root_logger="tests") middleware(request) message1 = json.loads(ctx.output[0], object_pairs_hook=dictionary)