2👍
It’s not clear where this log line comes from. As far as I can see, Django only logs 4xx and 5xx requests to django.request
logger. This doesn’t look like a gunicorn access log line either. And if you initiated this log line in your own code, you should be able to add the user easily.
So, here are a few generic solutions.
(Option 1) For a gunicorn access log line
You don’t have access to Django’s request object and thus won’t be able to retrieve the user from gunicorn. However, you can work it around by adding the user in the response headers.
yourapp/middleware.py
class UserHeaderMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
user = request.user
response['X-User'] = user.email if user.is_authenticated() else 'UNAUTHORIZED'
return response
yourproject/settings.py
MIDDLEWARE = [
...,
'django.contrib.auth.middleware.AuthenticationMiddleware',
..., # Your custom middleware must be called after authentication
'yourapp.middleware.UserHeaderMiddleware',
...,
]
Then change gunicorn’s access_log_format
setting to include this header. For instance: '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%({x-user}o)s"'
(Option 2) For django.request
logger
If your log line is sent to django.request
logger, there is a chance that it provided the request in the extra context.
In that case, you can write a custom Formatter to include the user:
yourapp/logging.py
from logging import Formatter
class RequestFormatter(Formatter):
def format(self, record):
request = getattr(record, 'request', None)
if user := getattr(request, 'user', None):
record.user = user.email if user.is_authenticated() else 'UNAUTHORIZED'
else:
record.user = '-'
return super().format(record)
yourapp/logging.py
LOGGING = {
...,
'formatters': {
...,
"django.request": {
"()": "yourapp.logging.RequestFormatter",
"format": "[{asctime}] {levelname} [{name}] {message} - {status_code} - {user}",
"style": "{",
},
},
'loggers': {
...,
"django.request": {
"handlers": ...,
"level": "INFO",
"formatter": 'django.request',
}
...,
},
}
(Option 3) Tell Django to log all requests in django.request
Django only logs 4xx and 5xx requests in django.request
. See source code
But we can change this behavior by using a custom WSGI handler.
In yourproject/wsgi.py you should have something like this:
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'yourproject.settings')
application = get_wsgi_application()
You can change this to use a custom WSGI handler:
import os
import django
from django.core.wsgi import WSGIHandler
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'yourproject.settings')
class CustomWSGIHandler(WSGIHandler):
def get_response(self, request):
# Note that this is only a copy of BaseHandler.get_response()
# without the condition on log_response()
set_urlconf(settings.ROOT_URLCONF)
response = self._middleware_chain(request)
response._resource_closers.append(request.close)
log_response(
"%s: %s",
response.reason_phrase,
request.path,
response=response,
request=request,
)
return response
django.setup(set_prefix=False)
application = CustomWSGIHandler()
Then, refer to Option 2 to include the user in the formatter.
(Option 4) Create a middleware to add a new log line
If don’t have access to this log line to update it and don’t have access to the request in the log formatter, you will have to add a new log line manually (and possibly silent the first one to avoid duplicates).
yourapp/middleware.py
import logging
logger = logging.getLogger('django.request')
class LoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
user_email = request.user.email if request.user.is_authenticated() else 'UNAUTHORIZED'
logger.info(f"{request.method} {request.path} - {request.status_code} - {user_email}")
return response
yourproject/settings.py
MIDDLEWARE = [
...,
'django.contrib.auth.middleware.AuthenticationMiddleware',
..., # Your custom middleware must be called after authentication
'yourapp.middleware.LoggingMiddleware',
...,
]
1👍
A custom middleware is how you can achieve this easily. You can do something like the below.
import logging
from loguru import logger # optional if you are not using it already.
from django.utils import timezone
logger = logging.getLogger('django.request')
class LogRequest:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
user = (
request.user.email
if request.user.is_authenticated
else "UNAUTHORIZED"
)
logger.info(
f"[{timezone.now().strftime('%d.%m.%Y %H:%M:%S')}] INFO [myapp.custom_logger] {request.method} {request.path} - {response.status_code} - {user}"
)
return response
You can then configure your activate your new middleware by registering it.
MIDDLEWARE = [
"django.middleware.gzip.GZipMiddleware",
"django.middleware.security.SecurityMiddleware",
...
"myapp.middleware.LogRequest",
]
This produces an output like the below.
[25.11.2022 15:57:37] INFO [myapp.custom_logger] GET /analyse/v2/ping - 200 - oppen@heimer.xyz
- [Django]-What's the correct include path in this template?
- [Django]-Benefit of using logging over print() to log information to Papertrail in a Django Heroku app
- [Django]-Django replica data save to many databases