Как я могу добавить текущее имя пользователя в журнал доступа в django.server?

Я пытаюсь добавить текущее имя пользователя, если оно есть, в журнал доступа приложения Django:

INFO [django.server:161] "GET / HTTP/1.1" 200 116181
                        ^ username should go here

Моя основная проблема заключается в том, как мне разделить пользователя/имя пользователя между промежуточным ПО и фильтром, поскольку в фильтре у меня нет доступа к объекту request?

У меня есть рабочее решение, использующее thread-local для хранения, но это не похоже на а хорошую идею

Тем более, что я не могу очистить значение в process_request, поскольку оно очищается слишком рано, до того, как будет напечатана строка журнала.

Решение с threading.local()

log.py

import logging
import threading

local = threading.local()


class LoggedInUsernameFilter(logging.Filter):
    def filter(self, record):
        user = getattr(local, 'user', None)
        if user and user.username:
            record.username = user.username
        else:
            record.username = '-'
        return True


class LoggedInUserMiddleware(object):

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        self.process_request(request)
        return self.get_response(request)

    def process_request(self, request):
        from django.contrib.auth import get_user
        user = get_user(request)
        setattr(local, 'user', user)

настройки джанго

MIDDLEWARE = (
    ...
    'commons.log.LoggedInUserMiddleware',
    ...
)

...

LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        ...
        'verbose_with_username': {
            'format': '[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(username)s %(message)s'
        }
    },
    'filters': {
        'logged_in_username': {
            '()': 'commons.log.LoggedInUsernameFilter',
        }
    },
    'handlers': {
        'console_with_username': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose_with_username',
            'stream': sys.stdout,
        },
    },
    'loggers': {
        ...
        'django.server': {
            'handlers': ['console_with_username'],
            'level': 'INFO',
            'propagate': False,
            'formatter': 'verbose_with_username',
            'filters': ['logged_in_username', ],
        },
        ...
    },
}

У меня есть рабочее решение, использующее thread-local для хранения данных, но это не кажется похожим на хорошую идею.

В некоторых ответах говорится, что это "чрезвычайно полезный" или "отличный метод", с оговоркой "асинхронный"

Даже авторитетное руководство по Python https://docs.python.org/3/howto/logging-cookbook.html#using-filters-to-impart-contextual-information предлагает потоково-локальное хранение:

Например, в веб-приложении обрабатываемый запрос (или, по крайней мере, интересные его части) может храниться в потоковой переменной (threading.local), а затем к нему можно обратиться из Filter, чтобы добавить, скажем, информацию из запроса - скажем, удаленный IP-адрес и имя пользователя удаленного пользователя - в LogRecord...

Также предлагается Использование LoggerAdapters и Использование contextvars, которые не подходят для django.server ведения журнала, который находится вне вашего контроля.

Поэтому asgiref.local.Local является хорошей заменой для оговорки "async".

# import threading
# local = threading.local()
from asgiref.local import Local
local = Local()
Вернуться на верх