-
Notifications
You must be signed in to change notification settings - Fork 1
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
Conversation
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.
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. |
There was a problem hiding this comment.
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`. |
There was a problem hiding this comment.
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). | |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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_
?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
request = self.request | ||
for attr in self.attributes: | ||
val = r_getattr(request, attr, "-") | ||
if self.always_add or val != "-": | ||
setattr(record, "request." + attr, val) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
def __init__(self, get_response: Callable[[WSGIRequest], Any], root: str = ""): | ||
self.root = root | ||
self.get_response = get_response |
There was a problem hiding this comment.
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(): |
There was a problem hiding this comment.
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
?
tests/test_request_middleware.py
Outdated
@@ -0,0 +1,62 @@ | |||
import json |
There was a problem hiding this comment.
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.
There was a problem hiding this 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 |
There was a problem hiding this comment.
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`. |
There was a problem hiding this comment.
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.
if not response: | ||
response = self.get_response(w_request) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
There was a problem hiding this 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.
I could verify that this approach does not work in a multithreaded environment. |
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.