A Django application in production without logging and monitoring is running blind. When something breaks at 3 AM, the difference between a 5-minute fix and a 3-hour investigation is the quality of your observability setup. This guide covers the logging and monitoring patterns I use on production Django projects: Python’s logging framework configuration, structured log output, error tracking with Sentry-style tools, performance monitoring, health check endpoints, database and queue monitoring, and the alerting strategies that wake you up for the right reasons. For broader deployment patterns, see the Deployment hub.
Good monitoring is not about collecting every metric. It is about answering two questions quickly: is the application healthy right now, and what changed when it stopped being healthy.
Django logging configuration
Django uses Python’s logging module. Configure it in settings with a dictionary:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'json': {
'()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
'format': '%(asctime)s %(levelname)s %(name)s %(message)s',
},
},
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'json_console': {
'class': 'logging.StreamHandler',
'formatter': 'json',
},
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler',
},
},
'root': {
'handlers': ['json_console'],
'level': 'WARNING',
},
'loggers': {
'django': {
'handlers': ['json_console'],
'level': 'WARNING',
'propagate': False,
},
'django.request': {
'handlers': ['json_console', 'mail_admins'],
'level': 'ERROR',
'propagate': False,
},
'myproject': {
'handlers': ['json_console'],
'level': 'INFO',
'propagate': False,
},
},
}
Structured logging
Plain text logs are difficult to parse and query. Use structured JSON logging in production:
pip install python-json-logger
With JSON logging, each log entry becomes a queryable object:
import logging
logger = logging.getLogger(__name__)
def process_order(order_id):
logger.info('Processing order', extra={
'order_id': order_id,
'action': 'process_order',
})
try:
# ... business logic ...
logger.info('Order processed', extra={
'order_id': order_id,
'status': 'success',
'duration_ms': elapsed,
})
except Exception as e:
logger.error('Order processing failed', extra={
'order_id': order_id,
'error': str(e),
}, exc_info=True)
raise
Structured logs integrate with log aggregation services (ELK, Datadog, CloudWatch) and enable filtering, alerting, and dashboards based on structured fields.
Application-level logging patterns
Log at boundaries where failures are likely:
# Log external API calls
logger.info('Calling payment API', extra={'amount': total, 'provider': 'stripe'})
response = payment_api.charge(amount=total)
logger.info('Payment API response', extra={'status': response.status_code})
# Log authentication events
logger.info('Login attempt', extra={'username': username, 'ip': get_client_ip(request)})
# Log slow operations
if elapsed_ms > 1000:
logger.warning('Slow operation detected', extra={
'operation': 'generate_report',
'duration_ms': elapsed_ms,
})
Do not log sensitive data: passwords, tokens, credit card numbers, or personal identifiers.
Error tracking
Sentry is the standard error tracking tool for Django:
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
sentry_sdk.init(
dsn=os.environ.get('SENTRY_DSN'),
integrations=[DjangoIntegration()],
traces_sample_rate=0.1,
send_default_pii=False,
)
Sentry captures unhandled exceptions, groups them by type and location, tracks occurrence frequency, and provides context like request data and user information.
Health check endpoints
Expose health check endpoints for load balancers and monitoring:
from django.http import JsonResponse
from django.db import connection
def health_check(request):
checks = {'status': 'healthy'}
# Database check
try:
with connection.cursor() as cursor:
cursor.execute('SELECT 1')
checks['database'] = 'ok'
except Exception:
checks['database'] = 'error'
checks['status'] = 'unhealthy'
# Cache check
try:
from django.core.cache import cache
cache.set('health_check', 'ok', 10)
if cache.get('health_check') == 'ok':
checks['cache'] = 'ok'
else:
checks['cache'] = 'error'
except Exception:
checks['cache'] = 'error'
status_code = 200 if checks['status'] == 'healthy' else 503
return JsonResponse(checks, status=status_code)
Map it to a URL that your load balancer polls:
urlpatterns = [
path('health/', health_check, name='health_check'),
]
Performance monitoring
Track request duration and throughput using middleware:
import time
import logging
logger = logging.getLogger('performance')
class RequestTimingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start = time.monotonic()
response = self.get_response(request)
duration = (time.monotonic() - start) * 1000
logger.info('Request completed', extra={
'method': request.method,
'path': request.path,
'status': response.status_code,
'duration_ms': round(duration, 2),
})
if duration > 2000:
logger.warning('Slow request', extra={
'path': request.path,
'duration_ms': round(duration, 2),
})
return response
Database monitoring
Track slow queries by enabling query logging:
LOGGING['loggers']['django.db.backends'] = {
'handlers': ['json_console'],
'level': 'DEBUG' if DEBUG else 'WARNING',
}
In development, Django Debug Toolbar shows all queries per request. In production, use database-level monitoring (pg_stat_statements for PostgreSQL) and application-level tracking. The PostgreSQL production guide covers database monitoring in detail.
Alerting strategy
Not every log message deserves an alert. Tier your alerts:
- Critical (page immediately): application down, database unreachable, error rate above 5%
- Warning (notify during hours): elevated error rate, slow responses, disk space below 20%
- Informational (review weekly): deployment events, dependency update reminders, unusual traffic patterns
Set alert thresholds based on your actual baseline, not arbitrary numbers. A 200ms average response time is normal for your application? Alert at 500ms, not at some generic “over 100ms” threshold.
Frequently asked questions
How much logging is too much? Log at boundaries and decision points, not inside tight loops. A good rule: if you would want to see this data during an incident investigation, log it. If it would just add noise, skip it.
Should I use Sentry or a general logging platform? Both. Sentry excels at error grouping, deduplication, and developer workflow. A logging platform (ELK, Datadog) handles structured log search, metrics, and dashboards. They complement each other.
How do I monitor Celery tasks? Use Flower for real-time monitoring, and send task completion and failure events to your logging pipeline. Track task duration, success rate, and queue depth as metrics.
What about APM (Application Performance Monitoring)? APM tools like New Relic or Datadog APM provide request tracing, database query analysis, and service maps. They are valuable for complex applications with multiple services. The Django integrations are typically straightforward to add.