Manual Integration

Full SDK integration with custom status updates and error handling.

Full SDK integration for cron monitoring with custom status updates and error handling. This approach gives you maximum control over what gets monitored and how.

Perfect for:

  • Custom cron logic not using standard libraries
  • Need fine-grained control over monitoring
  • Want to customize error handling and reporting
  • Serverless functions or cloud-based scheduling (AWS Lambda, Vercel, etc.)
  • Complex job workflows with multiple steps

Consider other approaches if:

  • Using node-cron, cron, or node-schedule → Try Automatic instead
  • Just need basic failure notifications → Try UI Setup instead
  • Managing many monitors programmatically → Try Advanced Setup instead

  1. Install and configure the Sentry SDK
  2. Create monitors in Sentry (or use programmatic creation)

First, create your monitor in Sentry:

  1. Go to AlertsCreate AlertCron Monitor
  2. Configure your monitor settings (name, schedule, timezone)
  3. Note the monitor slug for use in your code

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

// Wrap your job function
Sentry.withMonitor(
  "my-monitor-slug", // Monitor slug from Sentry
  async () => {
    console.log("Starting data processing...");

    // Your job logic here
    await processData();
    await generateReports();

    console.log("Job completed successfully");
  },
);

Requirements: SDK version 7.76.0 or higher.

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

async function runMyJob() {
  // 🟡 Notify Sentry your job is starting
  const checkInId = Sentry.captureCheckIn({
    monitorSlug: "my-monitor-slug",
    status: "in_progress",
  });

  try {
    console.log("Starting data processing...");

    // Your job logic here
    await processData();
    await generateReports();

    // 🟢 Notify Sentry your job completed successfully
    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "my-monitor-slug",
      status: "ok",
    });

    console.log("Job completed successfully");
  } catch (error) {
    // 🔴 Notify Sentry your job failed
    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "my-monitor-slug",
      status: "error",
    });

    // Also capture the error details
    Sentry.captureException(error);

    throw error; // Re-throw to maintain normal error handling
  }
}

// Schedule your job (with your preferred method)
setInterval(runMyJob, 60 * 60 * 1000); // Every hour

Note: Heartbeat monitoring only detects missed runs, not runtime timeouts.

Use your preferred scheduling method:

Copied
// Run every hour
setInterval(
  () => {
    Sentry.withMonitor("hourly-job", async () => {
      await runHourlyTask();
    });
  },
  60 * 60 * 1000,
);

Multi-Step Jobs with Detailed Reporting

For complex jobs with multiple phases:

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

async function runComplexJob() {
  const checkInId = Sentry.captureCheckIn({
    monitorSlug: "complex-job",
    status: "in_progress",
  });

  try {
    // Phase 1: Data extraction
    console.log("Phase 1: Extracting data...");
    Sentry.addBreadcrumb({
      message: "Starting data extraction phase",
      level: "info",
    });
    await extractData();

    // Phase 2: Data processing
    console.log("Phase 2: Processing data...");
    Sentry.addBreadcrumb({
      message: "Starting data processing phase",
      level: "info",
    });
    await processData();

    // Phase 3: Report generation
    console.log("Phase 3: Generating reports...");
    Sentry.addBreadcrumb({
      message: "Starting report generation phase",
      level: "info",
    });
    await generateReports();

    // Success
    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "complex-job",
      status: "ok",
    });
  } catch (error) {
    // Capture detailed error context
    Sentry.withScope((scope) => {
      scope.setTag("job_phase", getCurrentPhase());
      scope.setContext("job_details", {
        startTime: jobStartTime,
        recordsProcessed: recordCount,
      });

      Sentry.captureException(error);
    });

    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "complex-job",
      status: "error",
    });

    throw error;
  }
}
Environment-Specific Monitoring

Monitor the same job across multiple environments:

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

const environment = process.env.NODE_ENV || "production";

async function runEnvironmentAwareJob() {
  // Include environment in monitor context
  const checkInId = Sentry.captureCheckIn({
    monitorSlug: "environment-job",
    status: "in_progress",
    // Environment is automatically included from SDK config
  });

  try {
    // Environment-specific logic
    if (environment === "production") {
      await runProductionJob();
    } else {
      await runDevelopmentJob();
    }

    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "environment-job",
      status: "ok",
    });
  } catch (error) {
    Sentry.withScope((scope) => {
      scope.setTag("environment", environment);
      Sentry.captureException(error);
    });

    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "environment-job",
      status: "error",
    });

    throw error;
  }
}
Overlapping Jobs

Handle jobs that might run longer than their interval:

Copied
import * as Sentry from "@sentry/node";
import { v4 as uuidv4 } from "uuid";

async function runOverlappingJob() {
  // Generate a unique check-in ID for this execution
  const checkInId = uuidv4();

  Sentry.captureCheckIn({
    checkInId,
    monitorSlug: "long-running-job",
    status: "in_progress",
  });

  try {
    // Long-running job logic that might take longer than the interval
    await processLargeDataset();

    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "long-running-job",
      status: "ok",
    });
  } catch (error) {
    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "long-running-job",
      status: "error",
    });

    Sentry.captureException(error);
    throw error;
  }
}
Job Performance Monitoring

Track job performance metrics:

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

async function runPerformanceTrackedJob() {
  const transaction = Sentry.startTransaction({
    name: "cron-job-execution",
    op: "cron",
  });

  const checkInId = Sentry.captureCheckIn({
    monitorSlug: "performance-job",
    status: "in_progress",
  });

  try {
    // Track individual spans for job phases
    const extractSpan = transaction.startChild({
      op: "data-extraction",
      description: "Extract data from database",
    });

    await extractData();
    extractSpan.finish();

    const processSpan = transaction.startChild({
      op: "data-processing",
      description: "Process extracted data",
    });

    await processData();
    processSpan.finish();

    // Success
    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "performance-job",
      status: "ok",
    });
  } catch (error) {
    transaction.setStatus("internal_error");
    Sentry.captureException(error);

    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "performance-job",
      status: "error",
    });

    throw error;
  } finally {
    transaction.finish();
  }
}

Copied
async function runJobWithContext() {
  const checkInId = Sentry.captureCheckIn({
    monitorSlug: 'contextual-job',
    status: 'in_progress',
  });

  try {
    const records = await fetchRecords();
    
    for (const record of records) {
      // Set context for each record
      Sentry.withScope(scope => {
        scope.setTag('record_id', record.id);
        scope.setContext('record_data', {
          type: record.type,
          size: record.data.length,
        });
        
        // Process record - any errors will include this context
        await processRecord(record);
      });
    }
    
    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: 'contextual-job',
      status: 'ok',
    });
    
  } catch (error) {
    // Error already has context from withScope
    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: 'contextual-job',
      status: 'error',
    });
    
    throw error;
  }
}

Copied
async function runJobWithPartialFailures() {
  const checkInId = Sentry.captureCheckIn({
    monitorSlug: "partial-failure-job",
    status: "in_progress",
  });

  let hasErrors = false;
  const results = [];

  try {
    const tasks = await getTasks();

    for (const task of tasks) {
      try {
        const result = await processTask(task);
        results.push({ success: true, task: task.id, result });
      } catch (error) {
        hasErrors = true;
        results.push({
          success: false,
          task: task.id,
          error: error.message,
        });

        // Report individual task failures without failing the whole job
        Sentry.captureException(error, {
          tags: { task_id: task.id },
          extra: { partial_failure: true },
        });
      }
    }

    // Report overall job status
    if (hasErrors) {
      // Job completed but with some failures
      Sentry.addBreadcrumb({
        message: `Job completed with ${results.filter((r) => !r.success).length} failures`,
        level: "warning",
      });
    }

    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "partial-failure-job",
      status: hasErrors ? "error" : "ok",
    });
  } catch (error) {
    // Complete job failure
    Sentry.captureException(error);
    Sentry.captureCheckIn({
      checkInId,
      monitorSlug: "partial-failure-job",
      status: "error",
    });

    throw error;
  }

  return results;
}

From UI Setup

Before:

Copied
// Manual HTTP notifications
async function myJob() {
  await doWork();
  await fetch("https://sentry.io/api/.../cron/.../");
}

After:

Copied
// SDK integration
async function myJob() {
  Sentry.withMonitor("my-job", async () => {
    await doWork();
  });
}
From Automatic Instrumentation

Before:

Copied
// Library instrumentation
const cronWithCheckIn = Sentry.cron.instrumentNodeCron(cron);
cronWithCheckIn.schedule("0 2 * * *", doWork, { name: "my-job" });

After:

Copied
// Manual control
cron.schedule("0 2 * * *", () => {
  Sentry.withMonitor("my-job", doWork);
});

Was this helpful?
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").