Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PB-511: Django request context in logs - #minor #61

Closed
wants to merge 3 commits into from

Conversation

benschs
Copy link
Contributor

@benschs benschs commented Aug 26, 2024

Add django request context to logs by filter configuration and middleware. Middleware adds the filter with the current request to the log handlers. Any logs will have the request information also added to the log record.

Add django request context to logs by filter configuration and middleware.
Middleware adds the filter with the current request to the log handlers. Any
logs will have the request information also added to the log record.
@benschs benschs requested review from boecklic and ltshb August 26, 2024 15:10
@github-actions github-actions bot changed the title PB-511: Django request context in logs PB-511: Django request context in logs - #minor Aug 26, 2024
Test case for the option to always add the request attributes in the django
filter.
README.md Outdated

### Usage & Configuration

Add `logging_utilities.request_middleware.AddRequestToLogMiddleware` to the `settings.MIDDLEWARE` list.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe be a bit more precise and add a django config example.

README.md Outdated
@@ -507,6 +510,34 @@ filters:

**NOTE**: `JsonDjangoRequest` only support the special key `'()'` factory in the configuration file (it doesn't work with the normal `'class'` key).

## 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`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe to each log records to be more precise

README.md Outdated

| 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). |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mismatch between example and docu, is it request_attributes or attributes ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the attribute is not jsonifiable ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I change between request_attributes and attributes during development. It should now be attributes consistently.

from django.core.handlers.wsgi import WSGIRequest


def r_getattr(obj, attr, *args):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for what for is the prefix r_ ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer be more verbose here request_getattr

"""Initialize the filter

Args:
request_attributes: (WSGIRequest | None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be request here instead of request_attributes

Comment on lines 43 to 47
request = self.request
for attr in self.attributes:
val = r_getattr(request, attr, "-")
if self.always_add or val != "-":
setattr(record, "request." + attr, val)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not do this only if we have a request context ? meaning checking that self.request is not none

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no request, we only add the value "-" if always_add is explicitly set, so this is fine for me as it is.
I added a test case that explicitly sets None as request to demonstrate the behavior.

@@ -0,0 +1,115 @@
import logging
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put this in a new module django_middlewares/request_middleware.py

Comment on lines 36 to 38
def __init__(self, get_response: Callable[[WSGIRequest], Any], root: str = ""):
self.root = root
self.get_response = get_response
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a good idea to call root rootLogger to make the code easier to read/understand

Only include handlers that have at least one filter of type *filter_cls*.
"""
result = {}
for logger in self._find_handlers():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here should it not be handler instead of logger?

@@ -0,0 +1,62 @@
import json
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename to test_django_request_middleware.py

Rename files and variables for better readability and understanding.
@benschs benschs requested a review from ltshb August 27, 2024 14:02
Copy link

@boecklic boecklic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Do you have an idea about possible performance impact of this solution? Since it's doing the processing for every request.

self.always_add = always_add

def filter(self, record: LogRecord) -> bool:
request = self.request

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in __init__ the WSGIRequest is marked as optional and can be None if I understand correctly, so wouln't you need to check for None here and only continue if not None?

@@ -507,6 +509,44 @@ filters:

**NOTE**: `JsonDjangoRequest` only support the special key `'()'` factory in the configuration file (it doesn't work with the normal `'class'` key).

## Django request log records

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`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you could be a little more verbose about the basic idea behind your solution with middleware and filter. With some not very deep understanding of how logging filters work this is not immediately obvious.

Comment on lines +43 to +44
if not response:
response = self.get_response(w_request)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand this part: the process_request method already does a return self.get_response(request), so if that return doesn't evaluate to sth true, why should calling it again result in a different outcome?

return response

def _find_loggers(self) -> dict[str, logging.Logger]:
"""Return loggers part of root.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a little more information about what you do here, i.e. what root_logger is about would be helpful. This is not directly obvious also from django middleware doc I think

Copy link
Contributor

@ltshb ltshb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you verify if the filter approach is working in a multithread environment ? As discussed yesterday the logging loggers instance and filters are globals and it might be an issue with mutlithread environement.

@benschs
Copy link
Contributor Author

benschs commented Aug 28, 2024

Could you verify if the filter approach is working in a multithread environment ? As discussed yesterday the logging loggers instance and filters are globals and it might be an issue with mutlithread environement.

I could verify that this approach does not work in a multithreaded environment.
I will close this PR and open a new one with another approach of using thread variables.

@benschs benschs closed this Aug 28, 2024
@benschs benschs deleted the feat-PB-511-django-request-log branch August 29, 2024 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants