Skip to content
bucker

Cron monitoring

A cron monitor tells you when a scheduled job did not run, ran and failed, or started and never finished. The job reports in with check-ins; Bucker watches the clock. Every failure becomes an ordinary issue, with the same short id, assignment and alert routing as a crash.

There are four ways to send check-ins. They all land on the same service, so the same rules hold: a check-in with a schedule creates the monitor on first sight and keeps its schedule current after that.

You have Use
A Node job withMonitor from @bucker/node
node-cron tasks, BullMQ repeatable jobs instrumentNodeCron, instrumentBullMQ (automatic, below)
Celery beat tasks install(app, monitor_beat_tasks=True) (automatic, below)
A Python job with_monitor from bucker-sdk
Any command (shell, Go, Ruby, a binary) bucker-cron run
A crontab line and nothing else the heartbeat URL

Node

import { withMonitor, flush } from '@bucker/node'

await withMonitor(
  'nightly-export',
  () => exportEverything(),
  { schedule: { type: 'crontab', value: '0 2 * * *' }, checkinMargin: 5, maxRuntime: 30 },
)
await flush() // a job that exits right away must flush, or its last check-in is a false "missed"

withMonitor sends in_progress, then ok (or error if the callback throws; the error still propagates), under one check-in id and with the run's duration.

node-cron and BullMQ, without wrapping each job

import cron from 'node-cron'
import { instrumentNodeCron } from '@bucker/node/integrations/crons'

instrumentNodeCron(cron).schedule('0 2 * * *', nightlyExport, { name: 'nightly-export' })

instrumentNodeCron wraps schedule(): a task with a name option is bracketed like withMonitor and its crontab becomes the monitor's schedule. instrumentBullMQ(bullmq) returns the module with a Worker that does the same for jobs made by a repeat rule (pattern, every, upsertJobScheduler). Neither library is a dependency of the SDK. Details and limits are in the Node SDK.

Python

from bucker_sdk import with_monitor

@with_monitor("nightly-export", monitor_config={
    "schedule": {"type": "crontab", "value": "0 2 * * *"},
    "checkin_margin": 5, "max_runtime": 30, "timezone": "UTC",
})
def nightly_export(): ...

It is also a context manager (with with_monitor("one-off"): ...).

For Celery beat, bucker_sdk.celery.install(app, monitor_beat_tasks=True) brackets every task in beat_schedule with check-ins and derives the monitor schedule from the entry (a crontab, or an interval of a minute or more). See the Python SDK.

Any command: bucker-cron

@bucker/node ships a zero-dependency wrapper. It needs only Node 20 and a DSN:

export BUCKER_DSN="https://<public-key>@us.ingest.bucker.io/<project-id>"

# in your crontab, around the command you already run
0 2 * * * npx -y -p @bucker/node bucker-cron run nightly-backup --schedule "0 2 * * *" -- /opt/backup.sh --full

It sends in_progress, runs the command with its output untouched, then sends ok or error with the duration, and exits with the command's own exit code. If Bucker is unreachable it prints a warning and the command is unaffected: the monitor never becomes the outage.

Option Meaning
--dsn The project DSN (default $BUCKER_DSN)
--schedule "<cron>" Cron expression. Creates or updates the monitor
--interval-minutes <n> Expect a run every n minutes instead
--timezone <iana> Timezone for --schedule (default UTC)
--grace <minutes> Lateness tolerated before a run counts as missed
--max-runtime <minutes> How long an in-progress run may last before it times out
--environment <name> Environment tag

Without --schedule or --interval-minutes the monitor must already exist. A command that cannot start exits 127 and is reported as a failed run.

Plain HTTP

# the whole integration, in a crontab
0 2 * * * /opt/backup.sh && curl -fsS "https://us.ingest.bucker.io/api/<project-id>/monitors/nightly-backup/heartbeat?sentry_key=<public-key>"

POST /api/<project-id>/monitors/<slug>/checkins takes { status, checkInId?, durationMs?, environment?, monitorConfig? }; PUT .../checkins/<checkInId> closes a run. status is in_progress, ok or error. The heartbeat URL means "I just completed" (&status=error reports a failure).

The config in a check-in is an upsert

When a check-in carries a schedule (monitor_config in the SDKs, monitorConfig over HTTP) the monitor is created if it does not exist and updated if it does: the job's code is where the schedule is written down, so a changed crontab in your deploy reaches the monitor on the next ping instead of leaving it expecting the old time.

What an upsert changes: the schedule (cron expression or interval, timezone), and the grace period and max runtime when the config states them. A config that changes nothing writes nothing.

What it never changes: the monitor's name, whether it is enabled (a paused monitor stays paused), its owner, its thresholds and its mute. Those belong to people, in the console.

New monitors from a check-in are held to the console's rules: slugs are lowercase letters, digits, - and _ (up to 64), and a project has a ceiling on how many monitors it can hold.

What alerts, and when

A monitor opens an issue for a failed run (error), a missed run (nothing arrived within the grace period after the expected time) and a timeout (started, never finished within the max runtime). Each kind is its own issue. A successful run resolves them.

Three policy fields keep a flaky job from paging you, and tell Bucker who to tell:

  • Failure threshold (default 1): consecutive failed runs before an issue opens. One flaky run does not page when you set 2 or 3.
  • Recovery threshold (default 1): consecutive successful runs before the issue resolves.
  • Owner: a person or a team. An alert rule with a notify_owner action routes a monitor's issue to the owner you set here, and says so in the message.
  • Mute: silence a monitor until a time, up to 30 days. A muted monitor still records runs and opens issues; its alerts are written to the alert history as suppressed, naming the mute, so nothing is silently lost. Muting is never billed differently and never deactivates the monitor.

Set them on the monitor in the console, or PATCH the monitor (failureThreshold, recoveryThreshold, ownerPrincipalId, ownerTeamId, mutedUntil). Planned downtime is better expressed as a maintenance window, which silences everything it covers.

Sentry compatibility

An unmodified Sentry SDK's captureCheckIn and withMonitor already work: pointed at a Bucker DSN they arrive as check_in envelope items and go through the same service, including the upsert. Monthly and yearly intervals have no fixed length in minutes and are refused rather than guessed.