CrowdStrike is now FedRAMP High authorized. Secure your mission today. Learn more

In this section of our Python logging guide, we’ll take what we’ve learned in previous sections and apply it to a popular Python-based web framework: Django. Specifically, we’ll cover:

  • Django logging basics
  • Creating a basic Django app
  • Default Django logging behavior
  • Modifying the output of Django logs
  • Customizing a Django log configuration

Learn More

Introduction to Django logging

Django uses Python’s logging module by default. Familiar Python logging concepts like loggers, log levels, handlers, filters, and formatters apply to Django. Django’s default logging configuration gives developers a head start, and can be customized to meet application-specific requirements. The built-in Django loggers are:

  • django
    django: The root logger in the
    django
    django logging hierarchy, under which all of the following loggers are found.
  • django.request
    django.request: The logger for HTTP error messages. Server error messages (5XX HTTP codes) have ERROR-level severity. Client error messages (4XX HTTP codes) have WARNING-level severity.
  • django.server
    django.server: The logger for Django’s development web server, which is started with the
    runserver
    runserver command but should NOT be used in production.
  • django.template
    django.template: The logger for Django template rendering.
  • django.db.backends
    django.db.backends: The logger for database interactions.
  • django.security.*
    django.security.*: The logger that tracks Django security errors like those related to
    SuspiciousOperation
    SuspiciousOperation
    .

The default behavior of the

django.server
django.server is to emit logs of INFO severity and higher to the console, while the other loggers propagate logs up to the
django
django root logger. How those logs are emitted depends on the
DEBUG
DEBUG setting in your Django
settings.py
settings.py file:

  • If
    DEBUG = TRUE
    DEBUG = TRUE then messages of INFO or higher are emitted to the console.
  • If
    DEBUG = FALSE
    DEBUG = FALSE then messages of ERROR or higher are emitted to the console.

Working with logging in a Django app

Now that you know the basics of Django logging, let’s walk through a practical example of how to use it. For our example, we’ll use Windows 11, Python 3.10.10, and Django 4.1.7, but you can follow along on most modern Django development environments.

Warning: Our basic app is not intended for production use.

Create a basic Django app

For our basic app, we’ll make a few tweaks from the skeleton web app you get after you run

django-admin startproject <project_name>
django-admin startproject <project_name>. We’ll use the name
demoapp
demoapp for our project.

Create your
views.py
views.py file

To begin, we’ll create a

views.py
views.py file in our
demoapp
demoapp directory.

We add the following Python code to views.py file. This code is a slightly extended version of Django’s simple view example. The inline comments explain what each line does.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Import Django's HttpResponse class<p>from django.http import HttpResponse</p><p># Import Python's datetime module</p><p>import datetime</p><p># Create a function</p><p>def egg(request):</p><p># Get the day of the week, format it as a human-readable word</p><p>day = datetime.date.today().strftime('%A')</p><p># Create a basic HTML page with a title, H1 header,</p><p># and message that includes the day of the week</p><p>html = "<head><title>Pepper and egg is still great</title></head> </p><p><body> </p><p><h1> Breaking breakfast news! </h1> </p><p>Today is %a. <br> </p><p><b> Latest news: Pepper and egg is great </b> </p><p></body></html>" % day</p><p># Return the HTML content</p><p>return HttpResponse(html)</p>
# Import Django's HttpResponse class<p>from django.http import HttpResponse</p><p># Import Python's datetime module</p><p>import datetime</p><p># Create a function</p><p>def egg(request):</p><p># Get the day of the week, format it as a human-readable word</p><p>day = datetime.date.today().strftime('%A')</p><p># Create a basic HTML page with a title, H1 header,</p><p># and message that includes the day of the week</p><p>html = "<head><title>Pepper and egg is still great</title></head> </p><p><body> </p><p><h1> Breaking breakfast news! </h1> </p><p>Today is %a. <br> </p><p><b> Latest news: Pepper and egg is great </b> </p><p></body></html>" % day</p><p># Return the HTML content</p><p>return HttpResponse(html)</p>
Expand
# Import Django's HttpResponse class

from django.http import HttpResponse

# Import Python's datetime module

import datetime

# Create a function

def egg(request):

# Get the day of the week, format it as a human-readable word

day = datetime.date.today().strftime('%A')

# Create a basic HTML page with a title, H1 header,

# and message that includes the day of the week

html = "<head><title>Pepper and egg is still great</title></head>

<body>

<h1> Breaking breakfast news! </h1>

Today is %a. <br>

<b> Latest news: Pepper and egg is great </b>

</body></html>" % day

# Return the HTML content

return HttpResponse(html)

Update the
settings.py
settings.py file

Next, we’ll make some changes to our

settings.py
settings.py file.

  1. Set
    DEBUG = False
    DEBUG = False to turn off debug mode.
  2. Add
    localhost
    localhost
    to
    ALLOWED_HOSTS
    ALLOWED_HOSTS
    so we can access our server at
    localhost:8000
    localhost:8000
    with debug mode turned off.
  3. Append
    demoapp
    demoapp
    to the
    INSTALLED_APPS
    INSTALLED_APPS
    section so Django recognizes it.

Configure
urls.py
urls.py

The

urls.py
urls.py
file routes URLs to Django “views.” In our case, we’ll keep the default admin page and add a URL path pointing to the
egg
egg
function in our
views.py
views.py
file.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
from django.contrib import admin<p>from django.urls import path</p><p>from .views import egg</p><p>urlpatterns = [</p><p># point /admin to the default admin page</p><p>path('admin/', admin.site.urls),</p><p># make our egg function in views.py the home page</p><p>path('', egg, name='home')</p><p>]</p>
from django.contrib import admin<p>from django.urls import path</p><p>from .views import egg</p><p>urlpatterns = [</p><p># point /admin to the default admin page</p><p>path('admin/', admin.site.urls),</p><p># make our egg function in views.py the home page</p><p>path('', egg, name='home')</p><p>]</p>
Expand
from django.contrib import admin

from django.urls import path

from .views import egg

urlpatterns = [

# point /admin to the default admin page

path('admin/', admin.site.urls),

# make our egg function in views.py the home page

path('', egg, name='home')

]

Launch the Django app

Next, we run

python3 manage.py migrate
python3 manage.py migrate to apply the default migrations. If we were to run the app as is, without migration, Django would throw errors.

Then, we run the app with this command:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
python3 manage.py runserver
python3 manage.py runserver
Expand
python3 manage.py runserver

You should see output similar to the following:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Performing system checks...<p>System check identified no issues (0 silenced).</p><p>November 11, 2023 - 11:11:11</p><p>Django version 4.1.7, using settings 'demoapp.settings'</p><p>Starting development server at http://127.0.0.1:8000/</p><p>Quit the server with CTRL-BREAK.</p>
Performing system checks...<p>System check identified no issues (0 silenced).</p><p>November 11, 2023 - 11:11:11</p><p>Django version 4.1.7, using settings 'demoapp.settings'</p><p>Starting development server at http://127.0.0.1:8000/</p><p>Quit the server with CTRL-BREAK.</p>
Expand
Performing system checks...

System check identified no issues (0 silenced).

November 11, 2023 - 11:11:11

Django version 4.1.7, using settings 'demoapp.settings'

Starting development server at http://127.0.0.1:8000/

Quit the server with CTRL-BREAK.

On your development machine, use a web browser to access

http://localhost:8000
http://localhost:8000 and you should see a page similar to this:

You’ll notice that the console where you launched the app includes log messages for each HTTP request. That’s the default

django.server
django.server logger in action.

Update console logging

Now that we have a working app with basic console logging, let’s tweak the logging functionality.

First, we can update the severity of messages that print to the terminal. By default, the

django
django logger will display messages of ERROR level or higher if debug mode is off. We’ll make our modifications by adding a LOGGING dictionary to our
settings.py
settings.py
file. We’ll use a slightly modified version (different log level plus clarifying comments) of the example from Django’s official docs.

  1. Add
    import os
    import os
    to the top of the
    settings.py
    settings.py
    file in the
    demoapp
    demoapp
    project.
  2. Append the following code to the end of the
    settings.py
    settings.py
     file.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Create a LOGGING dictionary<p>LOGGING = {<!-- --></p><p># Use v1 of the logging config schema</p><p>'version': 1,</p><p># Continue to use existing loggers</p><p>'disable_existing_loggers': False,</p><p># Create a log handler that prints logs to the terminal</p><p>'handlers': {<!-- --></p><p>'console': {<!-- --></p><p>'class': 'logging.StreamHandler',</p><p>},</p><p>},</p><p># Define the root logger's settings</p><p>'root': {<!-- --></p><p>'handlers': ['console'],</p><p>'level': 'DEBUG',</p><p>},</p><p># Define the django log module's settings</p><p>'loggers': {<!-- --></p><p>'django': {<!-- --></p><p>'handlers': ['console'],</p><p>'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),</p><p>'propagate': False,</p><p>},</p><p>},</p><p>}</p>
# Create a LOGGING dictionary<p>LOGGING = {<!-- --></p><p># Use v1 of the logging config schema</p><p>'version': 1,</p><p># Continue to use existing loggers</p><p>'disable_existing_loggers': False,</p><p># Create a log handler that prints logs to the terminal</p><p>'handlers': {<!-- --></p><p>'console': {<!-- --></p><p>'class': 'logging.StreamHandler',</p><p>},</p><p>},</p><p># Define the root logger's settings</p><p>'root': {<!-- --></p><p>'handlers': ['console'],</p><p>'level': 'DEBUG',</p><p>},</p><p># Define the django log module's settings</p><p>'loggers': {<!-- --></p><p>'django': {<!-- --></p><p>'handlers': ['console'],</p><p>'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),</p><p>'propagate': False,</p><p>},</p><p>},</p><p>}</p>
Expand
# Create a LOGGING dictionary

LOGGING = {

# Use v1 of the logging config schema

'version': 1,

# Continue to use existing loggers

'disable_existing_loggers': False,

# Create a log handler that prints logs to the terminal

'handlers': {

'console': {

'class': 'logging.StreamHandler',

},

},

# Define the root logger's settings

'root': {

'handlers': ['console'],

'level': 'DEBUG',

},

# Define the django log module's settings

'loggers': {

'django': {

'handlers': ['console'],

'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),

'propagate': False,

},

},

}

Once you save the changes, you should see DEBUG-level messages print to the terminal.

Next, we’ll update the formatting of the log messages. To do this, we need to add a formatter to our logging dictionary and then add that formatter to our handler. Again, using an example from the Django docs, our formatter will use this format:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{levelname} {asctime} {module} {process:d} {thread:d} {message}
{levelname} {asctime} {module} {process:d} {thread:d} {message}
Expand
{levelname} {asctime} {module} {process:d} {thread:d} {message}
  • {levelname}
    {levelname}
    is the message's log level (WARNING, INFO, DEBUG, etc.).
  • {asctime}
    {asctime}
    is a date and time stamp in a
    YYYY-MM-DD HH:MM:SS,MS
    YYYY-MM-DD HH:MM:SS,MS
    format (e.g.,
    2023-11-11 11:11:11,123
    2023-11-11 11:11:11,123
    ).
  • {module}
    {module}
    is the module that emitted the log record.
  • {process:d}
    {process:d}
    is the process ID associated with the log record.
  • {thread:d}
    {thread:d}
    is the thread ID associated with the log record.
  • {message}
    {message}
    is the log message.

To implement these changes, follow these steps:

  1. Add a
    formatter
    formatter
    with this content after the
    disable_existing_loggers
    disable_existing_loggers
    section of the LOGGING dictionary in
    settings.py
    settings.py
    :
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    'formatters': {<!-- --><p>'verbose': {<!-- --></p><p>'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',</p><p>'style': '{',</p><p>},</p><p>},</p>
    'formatters': {<!-- --><p>'verbose': {<!-- --></p><p>'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',</p><p>'style': '{',</p><p>},</p><p>},</p>
    Expand
    'formatters': {

    'verbose': {

    'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',

    'style': '{',

    },

    },

  2. Add the new
    verbose
    verbose
    formatter to the existing
    console
    console
     handler.
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    'handlers': {<!-- --><p>'console': {<!-- --></p><p>'class': 'logging.StreamHandler',</p><p># Below is the line we've added</p><p>'formatter': 'verbose',</p><p>},</p><p>},</p>
    'handlers': {<!-- --><p>'console': {<!-- --></p><p>'class': 'logging.StreamHandler',</p><p># Below is the line we've added</p><p>'formatter': 'verbose',</p><p>},</p><p>},</p>
    Expand
        'handlers': {

    'console': {

    'class': 'logging.StreamHandler',

    # Below is the line we've added

    'formatter': 'verbose',

    },

    },

You should see messages similar to this print in your terminal:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
DEBUG 2023-11-11 11:11:11,111 autoreload 22512 31700 File C:UsersPepperAndEggAppDataLocalPackagesPythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0LocalCachelocal-packagesPython310site-packagesdjangocontribcontenttypesmigrations002_remove_content_type_name.py first seen with mtime 1611111111.5683823
DEBUG 2023-11-11 11:11:11,111 autoreload 22512 31700 File C:UsersPepperAndEggAppDataLocalPackagesPythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0LocalCachelocal-packagesPython310site-packagesdjangocontribcontenttypesmigrations002_remove_content_type_name.py first seen with mtime 1611111111.5683823
Expand
DEBUG 2023-11-11 11:11:11,111 autoreload 22512 31700 File C:UsersPepperAndEggAppDataLocalPackagesPythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0LocalCachelocal-packagesPython310site-packagesdjangocontribcontenttypesmigrations002_remove_content_type_name.py first seen with mtime 1611111111.5683823

Add logging to a file

Now that we know how to tweak Django logging settings, let’s modify where we send our logs. Output to the terminal can be useful, but it isn’t always the ideal location. For our example, printing only to the terminal means we aren’t persisting our logs if we need to review them in the future. Let’s update our Django logs to print to a file.

To send our logs to a file, we’ll need to first create the new handler in our

settings.py
settings.py LOGGING dictionary. First, we’ll append the following code to the
handlers
handlers
 section of the LOGGING dictionary:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Add a handler to write logs to a file<p>'file': {<!-- --></p><p># Use the FileHandler class</p><p>'class': 'logging.FileHandler',</p><p># Specify a local log file as a raw string. Use your app's directory.</p><p>'filename': r'C:PepperAndEggdemoappdemoappdjango.log',</p><p>},</p>
# Add a handler to write logs to a file<p>'file': {<!-- --></p><p># Use the FileHandler class</p><p>'class': 'logging.FileHandler',</p><p># Specify a local log file as a raw string. Use your app's directory.</p><p>'filename': r'C:PepperAndEggdemoappdemoappdjango.log',</p><p>},</p>
Expand
# Add a handler to write logs to a file

'file': {

# Use the FileHandler class

'class': 'logging.FileHandler',

# Specify a local log file as a raw string. Use your app's directory.

'filename': r'C:PepperAndEggdemoappdemoappdjango.log',

},

Note: update filename path to match your environment.

Next, we’ll add that handler to our

root
root logger and the
django
django
 logger:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Define the root logger's settings<p>'root': {<!-- --></p><p># Use the console and file logger</p><p>'handlers': ['console', 'file'],</p><p>'level': 'DEBUG',</p><p>},</p><p># Define the django log module's settings</p><p>'loggers': {<!-- --></p><p>'django': {<!-- --></p><p># Use the console and file logger</p><p>'handlers': ['console', 'file'],</p><p>'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),</p><p>'propagate': False,</p><p>},</p>
# Define the root logger's settings<p>'root': {<!-- --></p><p># Use the console and file logger</p><p>'handlers': ['console', 'file'],</p><p>'level': 'DEBUG',</p><p>},</p><p># Define the django log module's settings</p><p>'loggers': {<!-- --></p><p>'django': {<!-- --></p><p># Use the console and file logger</p><p>'handlers': ['console', 'file'],</p><p>'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),</p><p>'propagate': False,</p><p>},</p>
Expand
# Define the root logger's settings

'root': {

# Use the console and file logger

'handlers': ['console', 'file'],

'level': 'DEBUG',

},

# Define the django log module's settings

'loggers': {

'django': {

# Use the console and file logger

'handlers': ['console', 'file'],

'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),

'propagate': False,

},

When you’re done, the LOGGING dictionary in

settings.py
settings.py should look similar to this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Create a LOGGING dictionary<p>LOGGING = {<!-- --></p><p># Use v1 of the logging config schema</p><p>'version': 1,</p><p># Continue to use existing loggers</p><p>'disable_existing_loggers': False,</p><p># Add a verbose formatter</p><p>'formatters': {<!-- --></p><p>'verbose': {<!-- --></p><p>'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',</p><p>'style': '{',</p><p>},</p><p>},</p><p># Create a log handler that prints logs to the terminal</p><p>'handlers': {<!-- --></p><p>'console': {<!-- --></p><p>'class': 'logging.StreamHandler',</p><p># Add the verbose formatter</p><p>'formatter': 'verbose',</p><p>},</p><p># Add a handler to write logs to a file</p><p>'file': {<!-- --></p><p># Use the FileHandler class</p><p>'class': 'logging.FileHandler',</p><p># Specify a local log file as a raw string. Use your app's directory.</p><p>'filename': r'C:PepperAndEggdemoappdemoappdjango.log',</p><p>},</p><p>},</p><p># Define the root logger's settings</p><p>'root': {<!-- --></p><p># Use the console and file logger</p><p>'handlers': ['console', 'file'],</p><p>'level': 'DEBUG',</p><p>},</p><p># Define the django log module's settings</p><p>'loggers': {<!-- --></p><p>'django': {<!-- --></p><p># Use the console and file logger</p><p>'handlers': ['console', 'file'],</p><p>'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),</p><p>'propagate': False,</p><p>},</p><p>},</p><p>}</p>
# Create a LOGGING dictionary<p>LOGGING = {<!-- --></p><p># Use v1 of the logging config schema</p><p>'version': 1,</p><p># Continue to use existing loggers</p><p>'disable_existing_loggers': False,</p><p># Add a verbose formatter</p><p>'formatters': {<!-- --></p><p>'verbose': {<!-- --></p><p>'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',</p><p>'style': '{',</p><p>},</p><p>},</p><p># Create a log handler that prints logs to the terminal</p><p>'handlers': {<!-- --></p><p>'console': {<!-- --></p><p>'class': 'logging.StreamHandler',</p><p># Add the verbose formatter</p><p>'formatter': 'verbose',</p><p>},</p><p># Add a handler to write logs to a file</p><p>'file': {<!-- --></p><p># Use the FileHandler class</p><p>'class': 'logging.FileHandler',</p><p># Specify a local log file as a raw string. Use your app's directory.</p><p>'filename': r'C:PepperAndEggdemoappdemoappdjango.log',</p><p>},</p><p>},</p><p># Define the root logger's settings</p><p>'root': {<!-- --></p><p># Use the console and file logger</p><p>'handlers': ['console', 'file'],</p><p>'level': 'DEBUG',</p><p>},</p><p># Define the django log module's settings</p><p>'loggers': {<!-- --></p><p>'django': {<!-- --></p><p># Use the console and file logger</p><p>'handlers': ['console', 'file'],</p><p>'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),</p><p>'propagate': False,</p><p>},</p><p>},</p><p>}</p>
Expand
# Create a LOGGING dictionary

LOGGING = {

# Use v1 of the logging config schema

'version': 1,

# Continue to use existing loggers

'disable_existing_loggers': False,

# Add a verbose formatter

'formatters': {

'verbose': {

'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',

'style': '{',

},

},

# Create a log handler that prints logs to the terminal

'handlers': {

'console': {

'class': 'logging.StreamHandler',

# Add the verbose formatter

'formatter': 'verbose',

},

# Add a handler to write logs to a file

'file': {

# Use the FileHandler class

'class': 'logging.FileHandler',

# Specify a local log file as a raw string. Use your app's directory.

'filename': r'C:PepperAndEggdemoappdemoappdjango.log',

},

},

# Define the root logger's settings

'root': {

# Use the console and file logger

'handlers': ['console', 'file'],

'level': 'DEBUG',

},

# Define the django log module's settings

'loggers': {

'django': {

# Use the console and file logger

'handlers': ['console', 'file'],

'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'),

'propagate': False,

},

},

}

Once we save the changes, we’ll see logs print to a local

django.log
django.log file.

Next steps with Django logging

Now that you know the basics of Django logging, you can move on to applying them to real-world projects. Django logging gets even more powerful when combined with some of the other concepts we’ve discussed in this series. For example, you can centralize your Django logs and emit them to a platform like CrowdStrike Falcon® LogScale.

Learn More

Learn how to ingest data into CrowdStrike Falcon LogScale from your MacOS platform using Python. This guide will help you set up a one-node proof of concept so you can take advantage of LogScale's free trial!

Blog: How to Ingest Data into Falcon LogScale Using Python

Log your data with CrowdStrike Falcon Next-Gen SIEM

Elevate your cybersecurity with the CrowdStrike Falcon® platform, the premier AI-native platform for SIEM and log management. Experience security logging at a petabyte scale, choosing between cloud-native or self-hosted deployment options. Log your data with a powerful, index-free architecture, without bottlenecks, allowing threat hunting with over 1 PB of data ingestion per day. Ensure real-time search capabilities to outpace adversaries, achieving sub-second latency for complex queries. Benefit from 360-degree visibility, consolidating data to break down silos and enabling security, IT, and DevOps teams to hunt threats, monitor performance, and ensure compliance seamlessly across 3 billion events in less than 1 second.

Schedule Falcon Next-Gen SIEM Demo

Arfan Sharif is a product marketing lead for the Observability portfolio at CrowdStrike. He has over 15 years experience driving Log Management, ITOps, Observability, Security and CX solutions for companies such as Splunk, Genesys and Quest Software. Arfan graduated in Computer Science at Bucks and Chilterns University and has a career spanning across Product Marketing and Sales Engineering.