Error Handling and Logging

Introduction

Broken links return 404. Uncaught exceptions become 500. In development, Django's debug page helps you fix bugs; in production, users see friendly pages while operators read structured logs. This chapter adds custom error templates, handler404/handler500, and a practical LOGGING configuration—aligned with Flask error handling.

Prerequisites

DEBUG True vs False

DEBUGBehavior
True (development)Detailed traceback page, settings leak if exposed publicly
False (production)Generic error pages; you must configure logging and templates

Verify production readiness:

bash
python manage.py check --deploy

Fix warnings before going live.

Custom 404 and 500 Templates

Create project-level templates:

text
templates/
├── 404.html
└── 500.html

templates/404.html:

django
{% extends "blog/base.html" %}
{% block title %}Page not found{% endblock %}
{% block content %}
<h1>404 — Page not found</h1>
<p>We could not find that URL. Check the address or go home.</p>
<a href="{% url 'post-list' %}">Back to posts</a>
{% endblock %}

templates/500.html:

django
{% extends "blog/base.html" %}
{% block title %}Server error{% endblock %}
{% block content %}
<h1>Something went wrong</h1>
<p>Our team has been notified. Please try again later.</p>
{% endblock %}

Ensure TEMPLATES['DIRS'] includes BASE_DIR / "templates"project structure.

Django picks 404.html / 500.html automatically when DEBUG=False.

Handler Views in URLconf

Optional explicit handlers in config/urls.py:

python
handler404 = "blog.views.page_not_found"
handler500 = "blog.views.server_error"

blog/views.py:

python
from django.shortcuts import render
 
 
def page_not_found(request, exception):
    return render(request, "404.html", status=404)
 
 
def server_error(request):
    return render(request, "500.html", status=500)

JSON API branch (preview before DRF):

python
def page_not_found(request, exception):
    if request.path.startswith("/api/"):
        from django.http import JsonResponse
        return JsonResponse({"detail": "Not found."}, status=404)
    return render(request, "404.html", status=404)

Raising 404 in Views

python
from django.http import Http404
from django.shortcuts import get_object_or_404
 
def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk, published=True)
    ...

get_object_or_404 raises Http404—Django renders your 404 template.

Permission denied:

python
from django.core.exceptions import PermissionDenied
 
def delete_post(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if post.author != request.user:
        raise PermissionDenied
    ...

Default 403 page unless you add 403.html.

LOGGING Configuration

config/settings/base.py:

Create logs/ directory on server; add logs/*.log to .gitignore.

Code explanation:

  • django.request logs 4xx/5xx at WARNING+
  • blog logger matches logging.getLogger(__name__) in middleware — chapter 15

settings/development.py:

python
LOGGING["root"]["level"] = "DEBUG"

settings/production.py:

python
LOGGING["handlers"]["console"]["level"] = "INFO"

On Linux servers, systemd journalctl -u gunicorn captures console output — Linux monitoring.

Log Exceptions in Views

python
import logging
 
logger = logging.getLogger(__name__)
 
def risky_view(request):
    try:
        ...
    except ValueError as exc:
        logger.exception("Failed to process request user=%s", request.user)
        raise

logger.exception includes traceback—use in except blocks.

django.request and Admin Email (Optional)

When DEBUG=False, ADMINS receive email on 500 errors if SMTP is configured:

python
ADMINS = [("Ops", "ops@example.com")]
SERVER_EMAIL = "noreply@example.com"
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"

Prefer centralized logging (Sentry, CloudWatch) in modern ops.

Testing Error Pages Locally

Temporarily in development.py (revert after test):

python
DEBUG = False
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]

Visit a missing URL to see 404.html. Triggering 500 safely: raise in a test-only view.

Tip

Use django.views.debug Technical 500 View Only in Staging

Never enable DEBUG=True on a public server—even for a minute.

Post-Chapter Checklist

  • 404.html and 500.html exist under templates/
  • python manage.py check --deploy reviewed
  • LOGGING writes to console and rotating file
  • blog and django.request loggers configured

FAQ

Still see yellow debug page?

DEBUG=True in active settings module.

404 template ignored?

Wrong TEMPLATES['DIRS', or custom handler404 not returning status=404.

Logs empty on Gunicorn?

Workers may need --capture-output; check systemd StandardOutput=journal.

Log sensitive data?

Avoid logging passwords, tokens, full credit card numbers—scrub request.POST.

Sentry integration?

sentry-sdk with Django integration captures 500s with stack traces—recommended for production.