Вывод имени пользователя на stdout с помощью Django + Gunicorn Application
Прямо сейчас мое приложение Django + Gunicorn печатает только эту информацию:
[03.10.2022 19:43:14] INFO [django.request:middleware] GET /analyse/v2/ping - 200
Если запрос авторизован, я хотел бы показать также пользователя (имя пользователя/email) за кодом статуса, что-то вроде:
[03.10.2022 19:43:14] INFO [django.request:middleware] GET /analyse/v2/ping - 200 - useremail@outlook.com
Если запрос не авторизован, напишите UNAUTHORIZED:
[03.10.2022 19:43:14] INFO [django.request:middleware] GET /analyse/v2/ping - 200 - UNAUTHORIZED
Как я могу достичь этого с помощью комбинации Django и Gunicorn?
Спасибо
Настраиваемое промежуточное ПО - вот как вы можете легко добиться этого. Вы можете сделать что-то вроде следующего.
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
Затем вы можете настроить и активировать ваше новое промежуточное ПО, зарегистрировав его.
MIDDLEWARE = [
"django.middleware.gzip.GZipMiddleware",
"django.middleware.security.SecurityMiddleware",
...
"myapp.middleware.LogRequest",
]
В результате получается вывод, как показано ниже.
[25.11.2022 15:57:37] INFO [myapp.custom_logger] GET /analyse/v2/ping - 200 - oppen@heimer.xyz
Непонятно, откуда взялась эта строка журнала. Насколько я могу судить, Django регистрирует только запросы 4xx и 5xx в логгер django.request
. Это также не похоже на строку журнала доступа к gunicorn. И если вы инициировали эту строку журнала в своем собственном коде, вы должны быть в состоянии легко добавить пользователя.
Итак, вот несколько общих решений.
(Вариант 1) Для строки журнала доступа gunicorn
У вас нет доступа к объекту запроса Django, и поэтому вы не сможете получить пользователя из gunicorn. Однако вы можете обойти эту проблему, добавив пользователя в заголовки ответа.
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',
...,
]
Затем измените настройку gunicorn access_log_format
, чтобы включить этот заголовок. Например: '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%({x-user}o)s"'
(Вариант 2) Для django.request
регистратора
Если ваша строка журнала отправляется в django.request
логгер, есть вероятность, что он предоставил запрос в дополнительном контексте .
В этом случае вы можете написать пользовательский Formatter для включения пользователя:
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',
}
...,
},
}
(Вариант 3) Скажите Django регистрировать все запросы в django.request
Django регистрирует только запросы 4xx и 5xx в django.request
. Смотрите исходный код
Но мы можем изменить это поведение, используя пользовательский обработчик WSGI.
В вашем проекте/wsgi.py у вас должно быть что-то вроде этого:
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'yourproject.settings')
application = get_wsgi_application()
Вы можете изменить это, чтобы использовать пользовательский обработчик WSGI:
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()
Затем обратитесь к варианту 2, чтобы включить пользователя в форматер.
(Вариант 4) Создайте промежуточное ПО для добавления новой строки журнала
Если у вас нет доступа к этой строке журнала для ее обновления и нет доступа к запросу в форматере журнала, вам придется добавить новую строку журнала вручную (и, возможно, замолчать первую, чтобы избежать дублирования).
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',
...,
]