Cuckoo

Sentry Crons for Python: Send Check-ins to Cuckoo

Updated

If your Python jobs are already instrumented with the Sentry SDK's cron monitoring, you can send those check-ins to Cuckoo without rewriting the code. Cuckoo accepts Sentry check_in envelopes. Point the SDK at a Cuckoo DSN and every @monitor or capture_checkin call becomes a heartbeat. Missed, failed and overrunning jobs then alert through Cuckoo's email, Slack, Discord or webhook channels.

This guide covers the decorator, manual check-ins, Celery beat auto-instrumentation, and the one real catch: what happens to error events.

How Cuckoo sees Sentry check-ins

  • Your organisation has one DSN, shown in the Cuckoo dashboard: https://PUBLIC_KEY@cuckoo.metastic.ch/PROJECT_ID.
  • Monitors are matched by monitor slug. If a check-in carries a monitor_config, Cuckoo creates the monitor or updates it with the schedule, time zone, check-in margin and max runtime.
  • in_progress counts as a start, ok as success, error as failure. A missing check-in after schedule plus margin counts as late.
  • Only check-ins are accepted. Error and transaction events sent to this DSN are dropped.

Step 1: Initialise the SDK with the Cuckoo DSN

import sentry_sdk

sentry_sdk.init(
    dsn="https://PUBLIC_KEY@cuckoo.metastic.ch/PROJECT_ID",
    environment="production",
    # Only needed if the brotli package is installed: Cuckoo accepts gzip and deflate.
    _experiments={"transport_compression_algo": "gzip"},
)

Step 2: The @monitor decorator

monitor sends in_progress when the function starts, then ok or error when it ends. Exceptions are re-raised. With monitor_config, the monitor is created or updated automatically:

from sentry_sdk.crons import monitor

MONITOR_CONFIG = {
    "schedule": {"type": "crontab", "value": "0 3 * * *"},
    "timezone": "Europe/Zurich",
    "checkin_margin": 15,   # minutes of grace
    "max_runtime": 60,      # minutes before an in-progress run counts as failed
}

@monitor(monitor_slug="nightly-export", monitor_config=MONITOR_CONFIG)
def nightly_export():
    export_orders()

It also works as a context manager: with monitor(monitor_slug="nightly-export"):. Interval schedules use {"type": "interval", "value": 1, "unit": "hour"}, and the value must be an integer.

Step 3: Manual check-ins with capture_checkin

When start and end happen in different places, send the check-ins yourself and pass the returned id along:

from sentry_sdk.crons import capture_checkin
from sentry_sdk.crons.consts import MonitorStatus

check_in_id = capture_checkin(
    monitor_slug="hourly-sync",
    status=MonitorStatus.IN_PROGRESS,
    monitor_config={"schedule": {"type": "crontab", "value": "0 * * * *"}, "timezone": "UTC"},
)
try:
    sync_inventory()
    capture_checkin(monitor_slug="hourly-sync", check_in_id=check_in_id, status=MonitorStatus.OK)
except Exception:
    capture_checkin(monitor_slug="hourly-sync", check_in_id=check_in_id, status=MonitorStatus.ERROR)
    raise

Celery beat auto-instrumentation

CeleryIntegration(monitor_beat_tasks=True) reports every task in the beat schedule, with no decorators:

import sentry_sdk
from celery import signals
from sentry_sdk.integrations.celery import CeleryIntegration

@signals.beat_init.connect    # omit if beat runs inside the worker (-B)
@signals.celeryd_init.connect
def init_sentry(**kwargs):
    sentry_sdk.init(
        dsn="https://PUBLIC_KEY@cuckoo.metastic.ch/PROJECT_ID",
        integrations=[CeleryIntegration(monitor_beat_tasks=True, exclude_beat_tasks=["debug-.*"])],
    )

How it behaves:

  • The beat process sends in_progress when it dispatches a task, together with a monitor_config built from the crontab or interval schedule and the Celery time zone. Initialise the SDK in beat too, not only in workers.
  • The worker sends ok, or error on failure. Retries also send an error check-in.
  • The monitor slug is the beat schedule entry name. Use short, slug-like keys such as cleanup-sessions in beat_schedule.
  • Don't combine this with @monitor on the same task, or you get duplicate check-ins.

The catch: errors and the DSN

The SDK sends everything to one DSN per client. If you call sentry_sdk.init() with the Cuckoo DSN, your exceptions also go to Cuckoo and are dropped. Simple, honest options:

  1. Separate processes, separate DSNs. Run the scheduled jobs in their own process (a dedicated Celery worker and beat, or a cron container) initialised with the Cuckoo DSN. Your web app keeps its real Sentry DSN. Errors from the job process are then not tracked in Sentry. Log them, or use option 2 for that process.
  2. Keep Sentry for errors, use plain HTTP pings for heartbeats. Leave sentry_sdk.init() on your Sentry DSN and ping Cuckoo directly, for example requests.get("https://cuckoo.metastic.ch/ping/YOUR-UUID", timeout=10), plus /start and /fail. See the Celery beat and APScheduler guides for complete helpers.
  3. Use Cuckoo only for crons. If you don't use Sentry for errors at all, point the SDK at Cuckoo and you're done.

The SDK also has lower-level Client and Scope APIs, but integrations such as CeleryIntegration report through the globally initialised client. This guide doesn't depend on wiring a second client.

Schedule and margin advice

  • checkin_margin is Cuckoo's grace time in minutes. Start with the normal runtime plus a few minutes.
  • If you leave out checkin_margin or max_runtime, Cuckoo applies Sentry's defaults: a 1-minute margin (raised to Cuckoo's minimum if needed) and a 30-minute max runtime.
  • max_runtime only works when in_progress is sent, which @monitor and the Celery integration both do.
  • Always set timezone for crontab schedules, otherwise the schedule is read as UTC.

Troubleshooting

  • Monitor never appears: the first check-in had no monitor_config, so Cuckoo had nothing to create it from. Add the config, or create the monitor with that slug in the dashboard.
  • Celery tasks not reported: the SDK isn't initialised in the beat process, or the entry matches exclude_beat_tasks.
  • Errors missing in Sentry: that process uses the Cuckoo DSN. See the DSN section above.
  • HTTP 415 in the SDK debug log: the SDK compresses with brotli because the brotli package is installed. Set transport_compression_algo to gzip as in step 1.

FAQ

Do I need to change my monitor slugs when switching to Cuckoo?

No. Cuckoo matches monitors by slug and creates them from monitor_config, so existing decorators work as they are. Only the DSN changes.

Are failure_issue_threshold and recovery_threshold used?

Cuckoo creates monitors from schedule, time zone, check-in margin and max runtime. Other monitor_config keys are ignored, and alerts follow Cuckoo's own late, failure and recovery rules.

Can I mix SDK check-ins and plain ping URLs?

Yes. SDK monitors and URL-based checks live side by side in the same Cuckoo account, so you can migrate one job at a time.

Related guides

All guides · Compare Cuckoo with other tools

Send feedback

Ideas, bugs, questions: we read every message. Or write to hello@metastic.ch.