Django Logs made simple

Duration: 5-7 minutes
Level: beginner
Objective: configure basic logging system in a Django application

 

Getting started with Django can be daunting at first because of the vastly tools, settings and configurations which come with it. It’s like a swiss knife on steroids. You can do almost anything with it, but the variety of tools provided is intimidating at first.

Django Logs is such a tool. By the end of this article, you will understand the basics of it, how you can configure and use it with no fear. So let’s get started!

Log Level

Each log record (the message put in a logger) can be displayed on the console, put in a specific file or even sent to you via email. You can decide that considering the severity of the message. Each log record is included in one of the 5 categories (in ascending order):

  • DEBUG – low level info for debugging purposes
  • INFO – general system information
  • WARNING – minor problem that occurred
  • ERROR – major problem that occurred
  • CRITICAL – critical problem

Components

There are 4 actors involved in the logging process (the last 2 are out of the scope of this article):

  • Loggers
    • entry point into the logging system
    • you can look at it like a bucket where the logs are put into
    • you can have as many loggers as you want with different purposes
    • it has a defined log level – ignores the entries that are less important than the log level
  • Handlers
    • define a particular logging behavior (write to the console, send email, write to a network socket, etc)
    • also have a log level – will ignore the records with a lower log level
    • a logger can have multiple handlers (with different log levels) in order to provide different form of notifications
  • Filters
    • provide additional control over which log records are passed from logger to handler
    • ex. filter that modifies the logging record prior to being emitted (downgrades Error to Warning if a set of criteria is met
  • Formaters
    • format the record level text (for readability)
    • you can choose how to display the info about a log record – from the available log attributes  – (full list of logRecord attributes)

My scenario

Let’s say that I want to create a logger for my application. I’m not interested in the DEBUG messages so I’ll ignore that. I would like that the INFO and WARNING messages to be displayed in the console. All ERRORS should be written into a file. For though, I want to be notify by email.

For this, I need one logger, which would be my bucket for dropping messages. It’s log level would be INFO, because I want it to ignore DEBUG.

I also need 3 handlers, one for each behavior:

  • console – display all messages (log level: DEBUG)
  • file – writes the errors and the critical to a specific file (log level: ERROR)
  • mail admin – sends email when a critical error occurs (log Level: CRITICAL)

Configuration is very easy now that we now this concepts. Let’s put this in the settings.py file:

# LOGGING SETTINGS
# --------------------------------------------------------------
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
        },
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
        'file': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/tmp/django_info.log',
        },
    },
    'loggers': {
        'myAppName.custom': {
            'handlers': ['console', 'file', 'mail_admins'],
            'level': 'DEBUG',
        }
    }
}

# ADMINS
# ---------------------------------------------------------------
ADMINS = [
    ('Weekly Web Wisdom', 'wewewi@someemail.com'),
]

And, voila! We now have a working logger. To use it in code, all we need to do is import it.

logger = logging.getLogger('myApp.custom')

logger.debug("some unimportant debug message")
logger.info("just so you know")
logger.warning("be careful there")
logger.error("things are getting serious")
logger.critical("fire fire abandon ship!!")

Django’s own logging extension

In many cases,  the behavior we want from our loggers is pretty standard. That’s why,  in many situations, a custom one is not even required. Django has got us covered again. There are many built-in loggers which we can use so we won’t have to explicitly call the logger ourself. Bellow you will find a comprehensive example of a configuration which uses django.request (which automatically handles all errors originated from requests made in the app). You can find the full list here.

# LOGGING SETTINGS
# -----------------------------------------------------------------------
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,  # we don't want to override 
                                        # the default loggers
    'formatters': {  # the formaters (observe the different amount 
                      # of information we show in each situation)
        'hyper-verbose': {
            'format': '%(asctime)s %(created)f %(filename)s '
                      '%(funcName)s %(levelname)s %(levelno)s '
                      '%(lineno)d %(module)s %(msecs)d %(message)s '
                      '%(name)s %(pathname)s %(process)d %(processName)s '
                      '%(relativeCreated)d %(thread)d %(threadName)s',
        },
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s '
                      '%(process)d %(thread)d %(message)s',
        },
        'simple': {
            'format': '%(levelname)s %(message)s',
        },
    },
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
        },
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple',
        },
        'file': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/tmp/django.log',
            'formatter': 'hyper-verbose'
        },
        'file_info': {  # we can use different handlers with the 
                        # same purpose (here we put logs in a different file)
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/tmp/other_file_just_for_custom_events.log',
            'formatter': 'simple'
        }
    },
    'loggers': {
        'django': {  # the root of the loggers hierarchy 
                     # no messages are posted using this name but using 
                     # the names bellow
            'handlers': ['console',],
            'level': env.str('DJANGO_LOG_LEVEL', 'INFO'),
            'propagate': True,
        },
        'django.request': {   # we let django.request handle all 
                              # the logs related to requests
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': False, # we don't the error to propagate 
                                # to the next logger in the hierarchy
        },
        'myApp.custom': {
            'handlers': ['console', 'file_info', 'mail_admins'],
            'level': 'DEBUG',
        }
    }
}

 

And there it is. Now that we understand the mechanism behind it, even a comprehensive configuration like this has sense.

Please leave your questions and feedback bellow or subscribe here for more weekly web wisdom!

See you soon!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s