Monitoring
Errors with Sentry, logs and alarms on AWS, health checks, key checks, and Flower.
Sentry
Sentry captures errors and exceptions across Django, Celery, and Redis.
Configuration
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.redis import RedisIntegration
sentry_sdk.init(
dsn=env("SENTRY_DSN"),
integrations=[
LoggingIntegration(
level=logging.INFO, # Breadcrumbs from INFO
event_level=logging.ERROR, # Events from ERROR
),
DjangoIntegration(),
CeleryIntegration(),
RedisIntegration(),
],
environment=env("SENTRY_ENVIRONMENT", default="production"),
traces_sample_rate=env.float("SENTRY_TRACES_SAMPLE_RATE", default=0.0),
)Integrations
| Integration | What it captures |
|---|---|
DjangoIntegration | Unhandled exceptions in views, middleware errors |
CeleryIntegration | Task failures, retries, timeouts |
RedisIntegration | Redis connection errors |
LoggingIntegration | Python logging as breadcrumbs (INFO+) and events (ERROR+) |
Performance Monitoring
Set SENTRY_TRACES_SAMPLE_RATE to enable transaction tracing:
SENTRY_TRACES_SAMPLE_RATE=0.1 # Sample 10% of requestsLeave at 0.0 in production unless actively debugging performance — tracing adds overhead.
Environment Variables
| Variable | Default | Description |
|---|---|---|
SENTRY_DSN | — | Sentry project DSN |
DJANGO_SENTRY_LOG_LEVEL | INFO | Minimum log level for breadcrumbs |
SENTRY_ENVIRONMENT | production | Environment tag |
SENTRY_TRACES_SAMPLE_RATE | 0.0 | Transaction sample rate (0.0 - 1.0) |
Flower
Flower provides a real-time web dashboard for Celery monitoring.
What it shows
- Active, completed, and failed tasks
- Worker status and resource usage
- Task execution time and rate
- Task details (arguments, result, traceback)
- Broker (Redis) connection status
Access
| Environment | URL | Auth |
|---|---|---|
| Local | http://localhost:5555 | None |
| One server (Docker Compose) | https://yourdomain.com:5555 | Basic auth |
Flower isn't part of the AWS deployment.
Production Auth
CELERY_FLOWER_USER=admin
CELERY_FLOWER_PASSWORD=your-secure-passwordOn the one-server setup, Traefik exposes Flower on port 5555.
Logging
In production, every process logs to the console. On AWS, ECS sends that to CloudWatch, one log group per process: /ecs/<project>-<environment>/web (and worker, beat, frontend, migrate).
aws logs tail /ecs/hypersaas-staging/web --follow| Logger | Level | What it logs |
|---|---|---|
gunicorn.access | INFO | A line per request, without the health checks |
django.request | ERROR | Unhandled errors in requests (the 500s), with the traceback |
gunicorn.error | INFO | The server itself, including errors inside its workers |
backend.* | INFO | The app: chat, documents, subscriptions |
celery | INFO | Task execution |
A request line has the client's address (from the load balancer), the request, its status, size and time, and the user agent:
203.0.113.7 "GET /api/chat/sessions/ HTTP/1.1" 200 2 1ms "Mozilla/5.0 …"disable_existing_loggers in config/settings/production.py turns off every logger the config doesn't name. If you add a library whose logs you need, name its logger there too. With SENTRY_DSN set, Sentry also receives every ERROR as an event.
Alarms (AWS)
CloudWatch alarms email you through SNS (confirm the subscription email first) when:
- there are more than 10 server errors in 5 minutes
- a web or frontend task fails its health check for 3 minutes
- database CPU is above 80% for 15 minutes
- the database has less than 2 GB of free storage
A monthly AWS budget, from the bootstrap stack, emails at 80% of the forecast and at 100% of actual spend.
Health Checks
/healthz/
GET /healthz/ checks the database and the cache and answers 200 {"status": "ok"}, or 503 {"status": "error", "failed": ["database"]} naming the part that failed. It runs before the SSL redirect and the allowed-hosts check, so probes can call it over plain HTTP with any Host header, and it needs no sign-in.
- On AWS: the load balancer calls it every 15 seconds and stops sending traffic to a task that fails. ECS replaces a worker that stops answering a Celery ping. The frontend has its own check,
/api/health. - On one server: in
docker-compose.production.yml, postgres, redis, django and the worker have health checks, and Traefik checks django's/healthz/.
API keys
# One authenticated call per service; --generate also asks each AI provider for one token
docker compose exec django python manage.py check_keys --generateOn AWS, run it as a one-off task (see Running commands):
run python /app/manage.py check_keys --generateIt reports ok, not set or failed: <reason> for OpenAI, Anthropic, Gemini, Stripe, Resend, SerpApi, WeatherAPI, Google Maps and AWS, and exits 1 if anything failed. It never prints a key. Run it after setting up or rotating keys: --generate catches a valid key on an account that can't be billed, which listing models doesn't.
Locally (Docker Compose)
These check the local stack.
Application
# Django system check
docker compose exec django python manage.py check
# Database connectivity
docker compose exec django python manage.py check --database default
# API responding
curl -s http://localhost:8000/api/schema/ | head -1Celery
# Ping workers
docker compose exec django celery -A config.celery_app inspect ping
# Check active tasks
docker compose exec django celery -A config.celery_app inspect active
# Check scheduled tasks
docker compose exec django celery -A config.celery_app inspect scheduledDatabase
# PostgreSQL shell
docker compose exec postgres psql -U $POSTGRES_USER -d $POSTGRES_DB
# Check pgvector extension
SELECT * FROM pg_extension WHERE extname = 'vector';
# Check table sizes
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_stat_user_tables ORDER BY pg_total_relation_size(relid) DESC;Redis
# Redis CLI
docker compose exec redis redis-cli
# Check memory usage
redis-cli INFO memory
# Check connected clients
redis-cli INFO clients