Cuckoo

Sentry Crons for Node.js: Send Check-ins to Cuckoo

Updated

If your Node.js jobs already use Sentry's cron monitoring, you can send those check-ins to Cuckoo without touching the job code. Cuckoo accepts Sentry check_in envelopes. Point @sentry/node at a Cuckoo DSN and Sentry.withMonitor(), Sentry.captureCheckIn() and the cron library instrumentation report to Cuckoo. Late, failed and overrunning jobs then alert through email, Slack, Discord or webhooks.

There is one trade-off to understand first: what happens to error events. More on that below.

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. With a monitorConfig, Cuckoo creates the monitor or updates it with the schedule, time zone, check-in margin and max runtime.
  • in_progress counts as start, ok as success, error as failure. A missing check-in after schedule plus margin counts as late.
  • Only check-ins are accepted. Errors and transactions sent to this DSN are dropped.

Step 1: Initialise the SDK

// instrument.mjs — load before your app, e.g. node --import ./instrument.mjs worker.mjs
import * as Sentry from "@sentry/node";

Sentry.init({
  dsn: "https://PUBLIC_KEY@cuckoo.metastic.ch/PROJECT_ID",
  environment: "production",
});

Step 2: Sentry.withMonitor

withMonitor (SDK 7.76.0+) sends in_progress, runs your callback, then sends ok, or error if the callback throws or its promise rejects. The third argument is the monitor config used for the upsert:

import * as Sentry from "@sentry/node";

const monitorConfig = {
  schedule: { type: "crontab", value: "0 3 * * *" },
  timezone: "Europe/Zurich",
  checkinMargin: 15, // minutes of grace
  maxRuntime: 60,    // minutes before an in-progress run counts as failed
};

await Sentry.withMonitor("nightly-export", async () => {
  await exportOrders();
}, monitorConfig);

Interval schedules use { type: "interval", value: 2, unit: "hour" }.

Step 3: Manual check-ins with captureCheckIn

When start and end are in different places, send the check-ins yourself and keep the returned checkInId:

const checkInId = Sentry.captureCheckIn(
  { monitorSlug: "hourly-sync", status: "in_progress" },
  { schedule: { type: "crontab", value: "0 * * * *" }, timezone: "UTC", maxRuntime: 20 }
);

try {
  await syncInventory();
  Sentry.captureCheckIn({ checkInId, monitorSlug: "hourly-sync", status: "ok" });
} catch (err) {
  Sentry.captureCheckIn({ checkInId, monitorSlug: "hourly-sync", status: "error" });
  throw err;
}

In a short-lived script, call await Sentry.flush(2000) before the process exits so the last check-in is sent.

Auto-instrumentation for cron libraries

The SDK can wrap popular schedulers so every scheduled run sends check-ins. The monitor config comes from the schedule:

import * as Sentry from "@sentry/node";
import cron from "node-cron";
import { CronJob } from "cron";

// node-cron (SDK 7.92.0+): a `name` is required and becomes the monitor slug
const cronWithCheckIn = Sentry.cron.instrumentNodeCron(cron);
cronWithCheckIn.schedule("*/15 * * * *", () => refreshCache(), {
  name: "refresh-cache",
  timezone: "Europe/Zurich",
});

// cron package (SDK 7.92.0+): the second argument is the monitor slug
const CronJobWithCheckIn = Sentry.cron.instrumentCron(CronJob, "nightly-cleanup");
const job = new CronJobWithCheckIn("0 2 * * *", () => cleanup(), null, true, "Europe/Zurich");

node-schedule is covered by Sentry.cron.instrumentNodeSchedule() (SDK 7.93.0+). Check the Sentry docs for the library versions each helper supports. Wrapping only works when the scheduler runs inside a long-lived process. If that process dies, check-ins stop, and Cuckoo marks the monitors late after the margin.

The catch: errors and the DSN

Sentry.init() configures one DSN for the whole process. If that DSN is Cuckoo's, your exceptions go to Cuckoo and are dropped. Your options:

  1. Separate process, separate DSN. Run scheduled jobs in a dedicated worker process initialised with the Cuckoo DSN, and keep the web app on your real Sentry DSN. Errors from the worker are then not tracked in Sentry. Log them, or use option 2 for that worker.
  2. Keep Sentry for errors, ping Cuckoo over HTTP. Leave Sentry.init() on your Sentry DSN and call fetch("https://cuckoo.metastic.ch/ping/YOUR-UUID", { signal: AbortSignal.timeout(10_000) }), plus /start and /fail. The node-cron guide has a complete wrapper.
  3. Cuckoo only. If you don't use Sentry for errors, point the SDK at Cuckoo and you're done.

The SDK has lower-level client and scope APIs for running a second client, but the cron instrumentation and withMonitor report through the global client. This guide doesn't depend on that setup.

Schedule and margin advice

  • checkinMargin is the grace time in minutes. If you leave out checkinMargin or maxRuntime, Cuckoo applies Sentry's defaults: 1 minute (raised to Cuckoo's minimum if needed) and 30 minutes.
  • Set timezone for crontab schedules. Without it the schedule is read as UTC.
  • maxRuntime needs an in_progress check-in, which withMonitor and the instrumentation send.

Troubleshooting

  • Monitor never appears: the first check-in had no monitor config. Pass one, or create the monitor with that slug in the dashboard.
  • Last check-in missing in scripts: the process exited before the SDK sent it. Add await Sentry.flush().
  • Every run counted twice: several replicas run the same scheduler. Run it in one process.
  • Errors missing in Sentry: that process uses the Cuckoo DSN. See the DSN section above.

FAQ

Do I have to rename my monitor slugs?

No. Cuckoo matches by slug and creates monitors from the config, so existing slugs work unchanged. Only the DSN in Sentry.init() changes.

Are failureIssueThreshold and recoveryThreshold supported?

They are accepted but ignored. Cuckoo uses schedule, time zone, check-in margin and max runtime, and alerts follow its own late, failure and recovery rules.

Does this work in serverless functions?

Yes, as long as the check-ins are flushed before the function returns. For scheduled functions, withMonitor followed by await Sentry.flush(2000) is the simplest pattern.

Related guides

All guides · Compare Cuckoo with other tools

Send feedback

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