Django Custom Log Handler to DB Model

Django uses the standard python logging framework, which allows you to fire log events at different handlers, such as console, file, email, etc. We wanted to be able to store CRITICAL and ERROR messages to a custom model as part of our Django app, so that we could use the admin interface to easily view these critical alerts. To do so requires creating a custom log handler, as documented here.

Step 1:
Create your django model for where you want to store your error messages. For example, copy this in your application models.py file:
##############################################
class SystemErrorLog(models.Model):
    level = models.CharField(max_length=200)
    message = models.TextField()
    timestamp = models.DateTimeField('timestamp', null=True, blank=True)
##############################################
Step 2:
Create the custom log handler class. For example, copy this into a new loggers.py file that sits in your application folder, along side the models.py file.
##############################################
import logging
import datetime


class MyDbLogHandler(logging.Handler): # Inherit from logging.Handler
    def __init__(self):
        # run the regular Handler __init__
        logging.Handler.__init__(self)

    def emit(self, record):
        # instantiate the model
        try:
            #NOTE: need to import this here otherwise it causes a circular reference and doesn't work
            #  i.e. settings imports loggers imports models imports settings...
            from mydjangoapp.models import SystemErrorLog
            logEntry = SystemErrorLog(level=record.levelname, message=record.message, timestamp=datetime.datetime.now())
            logEntry.save()
        except:
            pass

        return
##############################################
Step 3:
Edit your settings.py LOGGING setting to configure your new logger. For example:
##############################################
    LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
        },
    'handlers': {
        'file':{
            'level': 'DEBUG',                      
            'class': 'logging.FileHandler',
            'filename': '/var/log/mylog.log',
            'formatter': 'verbose'
        },
        'db':{
            'level': 'ERROR',         
            'class': 'mydjangoapp.loggers.MyDbLogHandler',
            'formatter': 'verbose'
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['file'],
            'level': 'DEBUG',
            'propagate': False,
            },
        'myapplog': {
            'handlers': ['db'],   
            'level': 'DEBUG',
            'propagate': False,
            }
        }
}
##############################################
Step 4:
To view the log in the standard django admin interface, add the following to your django app admin.py file:
##############################################
class SystemErrorLogAdmin(admin.ModelAdmin):
    ordering = ('-timestamp',)
    list_display = ('level', 'message', 'timestamp',)
    list_filter = ('level',)
    search_fields = ('level','message',)

    def has_add_permission(self, request):
        return False

    def get_readonly_fields(self, request, obj=None):
        return self.readonly_fields + ('level', 'message', 'timestamp')
admin.site.register(SystemErrorLog, SystemErrorLogAdmin)
##############################################
Setting add permission to false disables the Add new button and actions. Setting the fields to read-only means people can't modify the entries by clicking on them.

Step 5:
As a final note, you may also want to have a housekeeping script that runs say nightly or weekly and deletes any records older than say 21 days, to make sure the database doesn't grow indefinitely. This can be done via a custom manage.py command. So for example, create a file called housekeeping.py and place it in your django app folder, in management/commands/housekeeping.py
##############################################
from django.core.management.base import NoArgsCommand, CommandError
from mydjangoapp.models import *
from django.conf import settings

from datetime import date, timedelta, datetime

import logging

class Command(NoArgsCommand):
    args = 'None.'
    help = 'Performs housekeeping operations.'

    def handle_noargs(self, *args, **options):
        # delete SystemErrorLog records older than 31 days
        cutoff = datetime.now() - timedelta(days=31)
        records = SystemErrorLog.objects.filter(timestamp__lt=cutoff)
        records.delete()
        return
##############################################
You can then invoke this from a console via python manage.py housekeeping. And you can also add this to say a nightly cronjob.

Comments

  1. in some place of code i wrote "raise forms.ValidationError("TEST EXCEPTION!")" - but it was not written to database. Did you checked this solution???

    ReplyDelete

Post a Comment

Popular posts from this blog

Wkhtmltopdf font and sizing issues

Import Google Contacts to Nokia PC Suite

Can't delete last blank page from Word