Django: Adding a Custom Attribute to All Log Lines

Django provides logging out of the box, and gives us the tools to customize what gets logged how.

This is not an in-depth primer on logging with Django— I’ve linked to the relevant documentation for the different pieces discussed here, but this is more an end-to-end implementation than a deep-dive — we’re going to look at how to add a custom attribute to all of our log lines. For example, if we have a multi-tenant application, we may want to know which organization performed each action for which there is a log line. So let’s implement this, by adding an organization_id to each log line.

Adding an attribute to a local thread

(The below is based on Django v. 1.10 — The syntax of this has changed with Django 2.0, so you may need to adjust using the documentation if you’re running a newer version.)

import threadinglocal = threading.local()
class OrganizationIdMiddleware(object):
def process_request(self, request):
organization_id = # logic to get attribute off request
setattr(local, 'organization_id', organization_id)

def process_response(self, request, response):
setattr(local, 'organization_id', None)
return response

We’ll then add our new middleware class to settings.py so that it gets run for each request/response cycle:

MIDDLEWARE_CLASSES = [
'<app>.logging.middleware.OrganizationIdMiddleware',
...
]

Filter

import loggingclass OrganizationIdFilter(logging.Filter):
def filter(self, record):
record.organization_id = getattr(local, 'organization_id', 'no_organization_id')
return True

Note that our filter function needs to return True, or the record won’t be passed on to the handler (below).

We can then add this filter to our logging set up in settings.py:

LOGGING = {
'filters': {
'organization_id': {
'()': 'everpath.logging.filters.OrganizationIdFilter'
},

# More to come
}

Formatter

LOGGING = {
'filters': {
'organization_id': {
'()': 'everpath.logging.filters.OrganizationIdFilter'
},
},
'formatters': {
'standard': {
'format': 'organization_id=%(organization_id)s ...',
},
},

Handlers

LOGGING = {
...
'filters': {
'organization_id': {
'()': 'everpath.logging.filters.OrganizationIdFilter'
},
...
'formatters': {
'standard': {
'format': 'organization_id=%(organization_id)s ...',
},
},
'handlers': {
'console': {
'level': 'INFO',
'filters': ['organization_id', ...],
'formatter': 'standard',
},

}

This will result in the organization_id attribute being present on all console log lines — the filter and formatter will need to be added to each handler on which we want the attribute, but each can be done differently if needs are different.

Happy logging!

Senior Software Engineer | www.adriennedomingus.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store