Skip to main content

Django: Setting Up Logging

Django: Setting Up Logging

Logging in Django is essential for monitoring application behavior, debugging issues, and tracking errors in production environments. Built on Python’s logging module and integrated with Django’s Model-View-Template (MVT) architecture, Django’s logging framework allows developers to capture detailed information about application events. This guide covers best practices for setting up logging in Django, including configuration, handlers, and production considerations, assuming familiarity with Django, Python, and basic logging concepts.


01. Why Set Up Logging in Django?

Logging provides visibility into application runtime behavior, enabling developers to diagnose errors, monitor performance, and audit security events. For Django applications (e.g., APIs, e-commerce platforms, or CMS), logging is critical to:

  • Track exceptions and application errors.
  • Monitor user activity and system events.
  • Debug issues in development and production.
  • Ensure compliance with auditing requirements.

Django’s logging integrates seamlessly with Python’s logging module, offering flexibility to log to files, consoles, or external services.

Example: Basic Logging Check

# views.py
import logging

logger = logging.getLogger(__name__)

def my_view(request):
    logger.info("Processing request in my_view")
    return render(request, 'template.html')

Output (Console):

[2025-05-16 21:21:00] INFO myapp.views: Processing request in my_view

Explanation:

  • logging.getLogger(__name__) - Creates a logger specific to the module.
  • logger.info - Logs an informational message, configurable to output to various destinations.

02. Key Logging Components

Django’s logging system builds on Python’s logging module, using loggers, handlers, formatters, and filters. The table below summarizes key components and their roles:

Component Description Purpose
Logger Named component for logging messages Entry point for logging
Handler Directs logs to destinations (e.g., file, console) Controls log output
Formatter Defines log message format Customizes log appearance
Filter Controls which logs are processed Refines log output
settings.py Configures logging Defines logging behavior


2.1 Configuring Logging in settings.py

Define logging behavior in settings.py using a LOGGING dictionary.

Example: Basic Logging Configuration

# myproject/settings.py
import os

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {message}',
            'style': '{',
        },
        'simple': {
            'format': '{levelname} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'simple',
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': os.path.join(BASE_DIR, 'logs', 'app.log'),
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'myapp': {
            'handlers': ['console', 'file'],
            'level': 'INFO',
            'propagate': False,
        },
        'django': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': False,
        },
    },
}
# Create logs directory
mkdir -p logs
touch logs/app.log

Output (app.log):

INFO 2025-05-16 21:21:00 myapp.views Processing request in my_view

Explanation:

  • formatters - Define log message formats (e.g., verbose includes timestamp).
  • handlers - Output logs to console and a file.
  • loggers - Configure logging for the myapp and django modules.
  • level - Sets minimum severity (e.g., INFO, DEBUG, ERROR).

2.2 Using Loggers in Code

Log messages from views, models, or other components.

Example: Logging in a View

# myapp/views.py
import logging
from django.shortcuts import render

logger = logging.getLogger(__name__)

def process_order(request):
    try:
        order_id = request.POST.get('order_id')
        logger.debug(f"Processing order {order_id}")
        # Process order logic
        logger.info(f"Order {order_id} processed successfully")
        return render(request, 'success.html')
    except Exception as e:
        logger.error(f"Error processing order {order_id}: {str(e)}", exc_info=True)
        return render(request, 'error.html')

Output (app.log):

INFO 2025-05-16 21:21:00 myapp.views Order 123 processed successfully
ERROR 2025-05-16 21:21:01 myapp.views Error processing order 124: Database error

Explanation:

  • logger.debug - Logs detailed debugging info (visible if level='DEBUG').
  • logger.error - Logs errors with stack traces (exc_info=True).
  • Use __name__ to create module-specific loggers.

2.3 Rotating Log Files

Prevent log files from growing indefinitely with rotating handlers.

Example: Rotating File Handler

# myproject/settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(BASE_DIR, 'logs', 'app.log'),
            'maxBytes': 1024 * 1024 * 5,  # 5 MB
            'backupCount': 5,
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'myapp': {
            'handlers': ['file'],
            'level': 'INFO',
            'propagate': False,
        },
    },
}

Output:

Log files rotated: app.log, app.log.1, app.log.2, etc.

Explanation:

  • RotatingFileHandler - Rotates logs when they reach 5 MB.
  • backupCount - Keeps up to 5 backup files.
  • Prevents disk space issues in production.

2.4 Logging in Docker

Configure logging for containerized Django applications.

Example: Docker Logging Setup

# docker-compose.yml
version: '3.8'
services:
  web:
    build: .
    command: gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - ./logs:/app/logs
    environment:
      - DEBUG=False
  redis:
    image: redis:7
# myproject/settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': '/app/logs/app.log',
            'maxBytes': 1024 * 1024 * 5,
            'backupCount': 5,
            'formatter': 'verbose',
        },
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'myapp': {
            'handlers': ['file', 'console'],
            'level': 'INFO',
            'propagate': False,
        },
    },
}
docker compose up -d

Output (Docker Logs):

web_1 | INFO 2025-05-16 21:21:00 myapp.views Processing request

Explanation:

  • Volumes mount logs directory to persist logs outside containers.
  • console handler - Outputs logs to Docker logs (docker logs).
  • file handler - Saves logs to /app/logs/app.log.

2.5 Sending Logs to External Services

Use third-party services like Sentry for centralized logging.

Example: Sentry Integration

pip install sentry-sdk
# myproject/settings.py
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from decouple import config

sentry_sdk.init(
    dsn=config('SENTRY_DSN'),
    integrations=[DjangoIntegration()],
    traces_sample_rate=1.0,
    send_default_pii=False,
)

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'myapp': {
            'handlers': ['console'],
            'level': 'ERROR',
            'propagate': False,
        },
    },
}
# views.py
import logging
logger = logging.getLogger(__name__)

def risky_view(request):
    try:
        # Risky operation
        raise ValueError("Test error")
    except Exception as e:
        logger.error(f"Error in risky_view: {str(e)}", exc_info=True)
        return render(request, 'error.html')

Output (Sentry Dashboard):

Error: Test error in risky_view with stack trace

Explanation:

  • sentry-sdk - Captures errors and sends them to Sentry.
  • DSN - Configures Sentry project endpoint via environment variable.
  • Ideal for production monitoring and error aggregation.

2.6 Incorrect Logging Configuration

Example: Missing File Handler Path

# settings.py (Incorrect)
LOGGING = {
    'version': 1,
    'handlers': {
        'file': {
            'class': 'logging.FileHandler',
            'filename': '/nonexistent/app.log',  # Invalid path
        },
    },
    'loggers': {
        'myapp': {
            'handlers': ['file'],
            'level': 'INFO',
        },
    },
}

Output:

PermissionError: [Errno 2] No such file or directory: '/nonexistent/app.log'

Explanation:

  • Invalid filename path causes a runtime error.
  • Solution: Ensure the log directory exists and is writable.

03. Effective Usage

3.1 Recommended Practices

  • Use environment-specific logging levels.

Example: Environment-Based Logging

# settings.py
from decouple import config

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'myapp': {
            'handlers': ['console'],
            'level': 'DEBUG' if config('DEBUG', cast=bool) else 'INFO',
            'propagate': False,
        },
    },
}

Output:

DEBUG logs in development; INFO logs in production
  • DEBUG level - Detailed logs for development.
  • INFO level - Concise logs for production to reduce noise.
  • Use filters to log specific events (e.g., security-related).

3.2 Practices to Avoid

  • Avoid logging sensitive data.

Example: Logging Sensitive Data (Incorrect)

# views.py (Incorrect)
import logging
logger = logging.getLogger(__name__)

def login_view(request):
    password = request.POST.get('password')
    logger.info(f"User login attempt with password: {password}")
    return render(request, 'login.html')

Output (app.log):

INFO 2025-05-16 21:21:00 views User login attempt with password: secret123
  • Logging passwords exposes sensitive data in logs.
  • Solution: Avoid logging user input or use a filter to redact sensitive fields.

04. Common Use Cases

4.1 Logging API Requests

Track API requests for monitoring and debugging.

Example: API Request Logging

# myapp/middleware.py
import logging

logger = logging.getLogger(__name__)

class RequestLoggingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        logger.info(f"Request: {request.method} {request.path}")
        response = self.get_response(request)
        logger.info(f"Response: {response.status_code}")
        return response
# settings.py
MIDDLEWARE = [
    ...,
    'myapp.middleware.RequestLoggingMiddleware',
]

Output (app.log):

INFO 2025-05-16 21:21:00 middleware Request: GET /api/products/
INFO 2025-05-16 21:21:00 middleware Response: 200

Explanation:

  • Middleware logs all HTTP requests and responses.
  • Useful for auditing API usage and diagnosing issues.

4.2 Logging Celery Tasks

Monitor asynchronous tasks executed by Celery.

Example: Celery Task Logging

# myapp/tasks.py
from celery import shared_task
import logging

logger = logging.getLogger(__name__)

@shared_task
def process_data(data_id):
    logger.info(f"Starting task for data ID {data_id}")
    try:
        # Process data
        logger.info(f"Completed task for data ID {data_id}")
        return True
    except Exception as e:
        logger.error(f"Task failed for data ID {data_id}: {str(e)}", exc_info=True)
        raise

Output (app.log):

INFO 2025-05-16 21:21:00 tasks Starting task for data ID 456
INFO 2025-05-16 21:21:01 tasks Completed task for data ID 456

Explanation:

  • Logs task start, success, or failure for debugging.
  • Integrates with Celery’s async workflow for monitoring.

Conclusion

Django’s logging framework, built on Python’s logging module, provides powerful tools for monitoring and debugging applications. Key takeaways:

  • Configure logging in settings.py with loggers, handlers, and formatters.
  • Use rotating file handlers and external services like Sentry for production.
  • Log strategically, avoiding sensitive data exposure.
  • Integrate logging with middleware or Celery for comprehensive monitoring.

With proper logging, you can maintain robust, observable Django applications! For more details, refer to the Django logging documentation and Python logging documentation.

Comments