Search code examples
pythondjangologgingdjango-logging

Can't add username to logging record using Middleware


I'm trying to log (by default) username and project (which can be decided from request object). I don't want to add context to every log manually.

The problem is that I can't make Django to add request or straight username and project to the LogRecord. I tried tens of ways.

This is my code:

middlewares.py

import threading
local = threading.local()

class LoggingRequestMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        setattr(local, 'request', request)
        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

settings.py

def add_username_to_log(record):
local = threading.local()
record.username = '-'
request = getattr(local,'request',None)
print(request)

return True

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': LOGGING_VERBOSE_FORMAT,
            'style': '{',
        },
    },
    'filters': {
        'context_filter': {
            '()': 'django.utils.log.CallbackFilter',
            'callback': add_username_to_log,
        },

    },
    'handlers': {
        'console': {
            'level': DEFAULT_LOG_LEVEL,
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
            'filters': ['context_filter'],
        },
        'file_main': {
            'level': DEFAULT_LOG_LEVEL,
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(LOG_PATH, 'main.log'),
            'maxBytes': DEFAULT_LOG_SIZE,
            'formatter': 'verbose',
            'filters': ['context_filter'],
            'backupCount': 0,
        },

    },
    'loggers': {
        '': {
            'handlers': ['file_main'],
            'level': DEFAULT_LOG_LEVEL,
            'propagate': False,
        },

    },
}

But the request object is always None. Do you know why?


Solution

  • threading.local() returns a new object every time, you have to read and write to the same object.

    locals_a = threading.local()
    locals_a.foo = 1
    hasattr(locals_a, 'foo')  # True
    locals_b = threading.local()
    hasattr(locals_b, 'foo')  # False
    

    You need to define your locals object in 1 place that you can then import everywhere you need to access the request and read and write to that object every time. As a basic example this should work

    def add_username_to_log(record):
        from middleware import local
        request = getattr(local,'request',None)