CronRadar for Celery
Auto-discovery for Celery periodic tasks. Add setup_cronradar(app) to your Celery app — every task with a Beat schedule is registered with CronRadar, watched for missed runs, and alerted on if it fails.
Built on top of the cronradar base SDK.
Install
pip install cronradar-celery
# or
poetry add cronradar-celery
# or
uv add cronradar-celery
Python 3.8+ supported. Celery 5.0+.
Setup
The SDK reads its API key from the CRONRADAR_API_KEY environment variable.
export CRONRADAR_API_KEY=ck_app_xxxxxxxxxxxxxxxxxxxx
Or pass it explicitly to setup_cronradar(...):
setup_cronradar(app, api_key='ck_app_xxxxxxxxxxxxxxxxxxxx')
Get the API key from the CronRadar dashboard at app.cronradar.com under your application's settings. API keys have the form ck_app_<random>.
Create a separate CronRadar application and API key for production, staging, and development. Store the key in the deployment platform's secret manager; never commit it, print it, or reuse a production key outside production.
The Celery integration runs once on worker / beat startup and listens to every task execution — no per-task initialization is needed.
Quickstart
Call setup_cronradar(app) once on your Celery app:
from celery import Celery
from cronradar_celery import setup_cronradar, skip_monitor
app = Celery('myapp', broker='redis://localhost:6379/0')
# Configure your Beat schedule as usual
app.conf.beat_schedule = {
'send-daily-report': {
'task': 'tasks.send_daily_report',
'schedule': '0 8 * * *', # 8am daily
},
}
# Hook CronRadar in
setup_cronradar(app)
@app.task
def send_daily_report():
generate_report()
That's it. On worker / Beat startup, the integration:
- Walks every entry in
app.conf.beat_schedule - Pre-syncs each scheduled task to CronRadar (so they appear in the dashboard immediately)
- Translates
crontab(...),schedule(timedelta(...)), and string schedules into standard 5-field POSIX cron - Connects to Celery's
task_prerun,task_success, andtask_failuresignals to recordstart/complete/failper execution
Decorators
Opt out per task
Decorate a Celery task with @skip_monitor to exclude it from auto-monitoring.
Order matters:
@app.taskmust come first (outermost),@skip_monitorsecond. Celery resolves the outer decorator at registration time; reversing the order breaks task discovery.
from cronradar_celery import skip_monitor
@app.task
@skip_monitor
def internal_cleanup():
# not monitored — internal/maintenance tasks you don't need alerts for
pass
Custom monitor keys
Monitor keys default to the Celery task name (lowercased, dots → dashes). Override the task name itself if you need a different key:
@app.task(name='critical-payment-processor')
def process_payments():
# registered in CronRadar as "critical-payment-processor"
pass
The integration uses task.name as the monitor key.
Reference
setup_cronradar(app, api_key=None)
Wires the integration to a Celery app. Called once at application startup.
| Parameter | Type | Required | Notes |
|---|---|---|---|
app | celery.Celery | yes | The Celery app instance. |
api_key | str | optional | API key. Falls back to CRONRADAR_API_KEY env var if omitted. |
What it does:
- Reads
app.conf.beat_scheduleand pre-syncs each entry to CronRadar. - Connects signal handlers (
task_prerun,task_success,task_failure) for lifecycle tracking.
Idempotent — safe to call multiple times if you have multiple Celery apps.
Grace period is configured per-monitor on CronRadar (default 60 seconds; set via the dashboard).
Decorators
| Decorator | Purpose |
|---|---|
@skip_monitor | Exclude this task from monitoring even if it has a Beat schedule. |
@skip_monitor must be placed after @app.task:
@app.task
@skip_monitor
def internal_cleanup():
return 'done'
Schedule translation
The integration accepts these schedule types in beat_schedule:
| Beat schedule | Translated to CronRadar |
|---|---|
'0 8 * * *' (string) | 0 8 * * * (passthrough) |
crontab(hour=8, minute=0) | 0 8 * * * |
crontab(hour=8, minute=0, day_of_week='mon-fri') | 0 8 * * 1-5 |
schedule(run_every=timedelta(hours=1)) | 0 * * * * |
If a schedule isn't translatable to standard cron (e.g., solar(...) schedules, sub-minute intervals), the integration logs a warning and that task is not monitored. Other tasks continue normally.
Configuration
| Environment variable | Required | Default | Purpose |
|---|---|---|---|
CRONRADAR_API_KEY | yes | — | API key from the CronRadar dashboard. Format ck_app_xxxxx. |
CRONRADAR_BASE_URL | no | https://cron.life | Override the ingestion endpoint. |
CRONRADAR_TIMEOUT | no | 5.0 | HTTP request timeout in seconds. |
CRONRADAR_LOG_ERRORS | no | 1 | Set to 0 to suppress stderr warnings on network failure. |
Error handling
The integration upholds the same hard guarantees as the base SDK:
- Never raises to user code. Network errors, timeouts, 4xx/5xx responses — all caught and logged. A failing CronRadar API must not break a Celery task.
- Never alters task execution. The integration uses Celery signals; it never modifies task state, results, or retry behavior. If your task raises, Celery's normal retry/error policy runs unchanged.
What this looks like at runtime:
| Situation | SDK behavior | Celery behavior |
|---|---|---|
| CronRadar API unreachable | Logs once; returns | Task runs and is marked SUCCESS/FAILURE normally |
| Sync on startup fails for one task | Logs; continues | Other tasks sync; worker starts |
| Per-task ping fails | Logs; returns | Task state unchanged |
| User task raises | Records fail_job with exception message | Celery applies retry/error policy |
| Beat schedule not translatable | Warns; that task is not monitored | Beat still triggers it normally |
Troubleshooting
Tasks aren't showing up in the CronRadar dashboard
- Confirm
setup_cronradar(app)is called once — typically right afterapp = Celery(...)and afterapp.conf.beat_schedule = {...}is set. - Confirm tasks are in
app.conf.beat_schedule. Tasks invoked viatask.delay(...)are not periodic and are not auto-monitored. Use the basecronradarSDK to instrument those manually. - Check
CRONRADAR_API_KEY— see "401 Unauthorized" in stderr if it's missing or wrong. - Verify Beat is actually running. Without Beat, no schedule fires, and CronRadar will eventually alert "monitor never executed."
@skip_monitor doesn't seem to apply
Decorator order. The outer decorator must be @app.task:
# correct
@app.task
@skip_monitor
def my_task():
return 'done'
# wrong — Celery never registers this as a task
@skip_monitor
@app.task
def my_task():
return 'done'
Some tasks log "schedule not translatable"
You're using a solar(...) schedule, a sub-minute interval, or a custom Beat schedule class. CronRadar monitors at minute resolution; these can't be expressed in standard cron. Either coarsen the schedule, or wrap the task body manually with the base SDK.
My task is @app.task(name='my.dotted.name') — what's the monitor key?
The monitor key matches Celery's task name, normalized to lowercase with dots replaced by dashes: my.dotted.name becomes my-dotted-name. Override with @monitor(key='explicit-key') to make it whatever you want.
How do I migrate from setup_cronradar(app, mode='all')?
Earlier versions accepted a mode argument with values like 'all' and 'opt-in'. The current model is MonitorAll (always) plus @skip_monitor to opt out. Migration:
- Drop the
modeargument fromsetup_cronradar()calls. - Add
@skip_monitorto any task that was previously not covered undermode='opt-in'.
The mode argument is removed in this version; passing it raises TypeError, which is intentional — agents reading old snippets get a clear signal to update.
Verification
- Deploy or restart both the Celery worker and Beat after calling
setup_cronradar(app). - Confirm the periodic task appears in the intended CronRadar application with its Beat schedule.
- Run the task through Celery and confirm a terminal
successorfailedresult; discovery alone is not activation. - Use CronRadar's guided reliability drill to verify notification delivery without changing the production task.
To test authentication and outbound HTTPS without printing the key:
curl --fail-with-body --silent --show-error --user "$CRONRADAR_API_KEY:" "https://cron.life/api/v1/sync/status"
Migration
If upgrading from selective or mode='all' setup, remove the mode argument and call setup_cronradar(app) once. All Beat-scheduled tasks are monitored by default; keep @skip_monitor only on intentional opt-outs. Deploy, confirm one reconciled registry and terminal run, then remove any old manual heartbeats that use the same task key.
Links
- Documentation: docs.cronradar.com/integrations/celery
- Monitoring overview: cronradar.com/celery
- Agent-friendly index: docs.cronradar.com/llms.txt
- OpenAPI spec: docs.cronradar.com/openapi.json
- PyPI: pypi.org/project/cronradar-celery
- GitHub: github.com/cronradar/cronradar-celery
- Base SDK: pypi.org/project/cronradar
- Support: [email protected]