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
- Settings —
DEBUG,ALLOWED_HOSTS - Templates
- URLs and Routing
DEBUG True vs False
DEBUG | Behavior |
|---|---|
True (development) | Detailed traceback page, settings leak if exposed publicly |
False (production) | Generic error pages; you must configure logging and templates |
Verify production readiness:
python manage.py check --deployFix warnings before going live.
Custom 404 and 500 Templates
Create project-level templates:
templates/
├── 404.html
└── 500.htmltemplates/404.html:
{% 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:
{% 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:
handler404 = "blog.views.page_not_found"
handler500 = "blog.views.server_error"blog/views.py:
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):
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
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:
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.requestlogs 4xx/5xx at WARNING+bloglogger matcheslogging.getLogger(__name__)in middleware — chapter 15
settings/development.py:
LOGGING["root"]["level"] = "DEBUG"settings/production.py:
LOGGING["handlers"]["console"]["level"] = "INFO"On Linux servers, systemd journalctl -u gunicorn captures console output — Linux monitoring.
Log Exceptions in Views
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)
raiselogger.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:
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):
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.htmland500.htmlexist undertemplates/ -
python manage.py check --deployreviewed -
LOGGINGwrites to console and rotating file -
bloganddjango.requestloggers 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.