Django logging json format


Install package json-log-formatter

$ pip install json-log-formatter

Configure settings

Create file log.py

import os
import time
import logging


class JSONFormatter(json_log_formatter.VerboseJSONFormatter):
    def json_record(self, message, extra, record):
        additional_data = {"level": record.levelname,
                           "pathname": record.pathname,
                           "lineno": record.lineno}

        if request := extra.pop("request", None):
            additional_data.update({
                "user": str(request.user),
                "remote_addr": request.META.get("REMOTE_ADDR"),
                "method": request.META.get("REQUEST_METHOD"),
                "path": request.path,
                "server_protocol": request.META.get('SERVER_PROTOCOL'),
                "server_name": request.META.get('SERVER_NAME'),
                "content_type": request.META.get('CONTENT_TYPE'),
                "http_user_agent": request.META.get('HTTP_USER_AGENT'),
                "hostname": os.uname().nodename,
                "python": os.getenv("PYTHON_VERSION"),
                "timestamp": time.time()
            })
        extra.update(additional_data)

        return super().json_record(message, extra, record)

In settings.py update LOGGING, add formatter

"json": {
    "()": "log.JSONFormatter",
},

Full LOGGING example

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {lineno} {process} {message}',
            "style": "{",
        },
        "json": {
            "()": "log.JSONFormatter",
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'formatter': 'json',
            'class': 'logging.StreamHandler',
        }
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'propagate': True,
            'level': "WARNING",
        },
        'django.request': {
            'handlers': ['console'],
            'level': LOGLEVEL,
            'propagate': False,
        },
        'django.server': {
            'handlers': ['console'],
            'level': "ERROR",
            'propagate': False,
        },
         "": {
             "level": LOGLEVEL,
             "handlers": ['ntfy', 'console'],
             "propagate": False,
         }
    }
}

Create logging base class

class MetaLog(type):
    log = None
    logger_name = None

    def __new__(cls, name, bases, dct):
        x = super().__new__(cls, name, bases, dct)
        x.log = logging.getLogger(cls.logger_name or __name__)
        return x


class Log(metaclass=MetaLog):
    def logs(self, msg, level="info"):
        return self.log.log(
            logging.getLevelName(level.upper()),
            msg,
            extra={"request": self.request})

Use logging in django classes

class MainTemplateView(TemplateView, Log):
    def get(self, request, *args, **kwargs):
        self.logs("Test message")
        self.log.info("Test message", extra={"request": self.request})
        return super().get(request, *args, **kwargs)

Output example

{
  "level": "INFO",
  "pathname": "/home/alex/venv/pay/src/pay/mixin.py",
  "lineno": 233,
  "user": "AnonymousUser",
  "remote_addr": "127.0.0.1",
  "method": "GET",
  "path": "/",
  "server_protocol": "HTTP/1.1",
  "server_name": "localhost",
  "content_type": "text/plain",
  "http_user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
  "hostname": "acer",
  "python": null,
  "timestamp": 1704395939.1455264,
  "filename": "mixin.py",
  "funcName": "get",
  "levelname": "INFO",
  "module": "mixin",
  "name": "pay.mixin",
  "process": 13893,
  "processName": "MainProcess",
  "stack_info": null,
  "thread": 140252349982272,
  "threadName": "Thread-3 (process_request_thread)",
  "message": "Test message",
  "time": "2024-01-04T19:18:59.145540"
}