Skip to content

About

Celery task monitoring inside Django admin - zero extra infrastructure.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

django-taskpulse

Celery task monitoring inside the Django admin you already run. No Flower, no extra process, no broker polling — just a reusable app that records task lifecycle events via Celery signals.

CI License: MIT Python Django

Why

Flower is a separate service to deploy, secure and keep running. Most Django + Celery teams just want to answer three questions:

  • Which tasks are failing, and with what traceback?
  • Which tasks are retrying more than they should?
  • Which tasks are slow (avg / p95)?

TaskPulse answers those from the Django admin, using only your existing database. Workers write one row per task id as tasks move through PENDING → STARTED → SUCCEEDED / FAILED / RETRIED.

Install

pip install django-taskpulse

Or from source:

pip install git+https://github.com/dilenshah23/django-taskpulse.git

Quickstart

  1. Add the app and migrate:
# settings.py
INSTALLED_APPS = [
    # ...
    "django_taskpulse",
]
python manage.py migrate django_taskpulse
  1. That's it. The app connects to Celery's task_received, task_prerun, task_postrun, task_failure and task_retry signals when Django starts, so any process that runs your Django app (including your Celery workers, assuming the standard django-celery setup where workers load your settings) starts recording task runs immediately.

  2. Open Admin → TaskPulse → Task runs to browse runs, and the Stats button on that page for per-task aggregates.

What you get

Task run log

A read-only admin changelist with:

  • colored state badges (PENDING, STARTED, SUCCEEDED, FAILED, RETRIED)
  • filters by state and task name, plus a date hierarchy on queue time
  • search by task id or task name
  • runtime, retry count, worker hostname and truncated args/kwargs per run
  • full exception traceback text on failed runs

Rows are keyed by Celery task id, so a task that retries stays a single row with an incrementing retry counter instead of N duplicates.

Stats dashboard

Admin → Task runs → Stats aggregates the recent window (24h by default): counts by state, failure rate, and avg/p95 runtime per task name, worst failure rate first. The same numbers are available programmatically:

from django_taskpulse.stats import compute_stats

for row in compute_stats(window_hours=24):
    print(row.task_name, f"{row.failure_rate_percent:.1f}%", row.p95_runtime)

Retention

Task history is pruned with a management command, typically from cron or Celery beat:

python manage.py taskpulse_purge            # honors TASKPULSE_RETENTION_DAYS
python manage.py taskpulse_purge --days 7   # explicit override
python manage.py taskpulse_purge --dry-run  # report only

Settings

All settings are optional and read lazily:

TASKPULSE_ENABLED = True          # master switch; receivers no-op when False
TASKPULSE_CAPTURE_ARGS = True     # store repr() of args/kwargs
TASKPULSE_MAX_ARG_LENGTH = 512    # truncation limit for each stored repr
TASKPULSE_RETENTION_DAYS = 30     # used by `taskpulse_purge`
TASKPULSE_STATS_WINDOW_HOURS = 24 # look-back window for the stats dashboard

Set TASKPULSE_CAPTURE_ARGS = False if task arguments may contain secrets or personal data.

How it works

Celery emits signals inside the worker for every task lifecycle event. TaskPulse's AppConfig.ready() connects five receivers that upsert a TaskRun row keyed by task id:

  • task_received → row created as PENDING with queue time and arg reprs
  • task_prerun → STARTED, records start time and worker hostname
  • task_retry → RETRIED, increments the retry counter, stores the reason
  • task_failure → FAILED, stores the traceback text
  • task_postrun → sets finish time, computes runtime, finalizes state

Receivers never raise: a monitoring failure is logged and the task proceeds untouched. There is no polling and no message inspection — if your worker can reach your database, TaskPulse works.

Caveats

  • Recording happens in the worker, so tasks queued but never picked up by a worker won't appear (there is nothing to observe them). task_received covers the queued-but-not-yet-started window on busy workers.
  • One extra DB write per lifecycle event. For very high-volume tasks (hundreds/sec), either disable TaskPulse for those workers or accept the write load; there is no sampling yet.
  • With task_always_eager (tests, local dev), task_received doesn't fire — rows are created at task_prerun instead.

Example project

The example/ directory contains a minimal wired-up project with demo tasks that succeed, fail, retry and sleep:

cd example
pip install -e "..[dev]"
python manage.py migrate
python manage.py createsuperuser
python manage.py demo_seed        # run demo tasks eagerly, no broker needed
python manage.py runserver

Then open http://127.0.0.1:8000/admin/django_taskpulse/taskrun/. To exercise a real worker instead, set CELERY_BROKER_URL (e.g. Redis) and run celery -A config worker -l info alongside.

Compatibility

  • Python 3.10 – 3.12
  • Django 4.2+ and 5.x
  • Celery 5.x

Runtime dependencies are Django and Celery only.

Development

git clone https://github.com/dilenshah23/django-taskpulse.git
cd django-taskpulse
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
pytest
ruff check .

Pre-commit hooks (ruff, ruff format, isort) are configured in .pre-commit-config.yaml.

License

MIT © Dilen Shah

About

Celery task monitoring inside Django admin - zero extra infrastructure.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages