Facebook Pixel

Cron Job Error Handling and Monitoring Reliable Scheduled Tasks in Node.js

Learn how to handle errors and monitor cron jobs in Node.js try-catch, alerts, logging, retry logic, and ensuring tasks run reliably.

Cron Job Error Handling and Monitoring

Cron jobs run unattended, so error handling and monitoring are critical. This guide covers best practices for reliable cron jobs.

1. Always Use try-catch

cron.schedule("0 0 * * *", async () => { try { console.log("Starting daily cleanup..."); await runCleanup(); console.log("Cleanup completed successfully"); } catch (err) { console.error("Cleanup failed:", err.message); console.error(err.stack); // Send alert await sendAlert("Daily cleanup failed: " + err.message); } });

2. Log Everything

const winston = require("winston"); const logger = winston.createLogger({ level: "info", format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.Console(), new winston.transports.File({ filename: "logs/cron.log" }) ] }); cron.schedule("0 0 * * *", async () => { logger.info("Cron job started", { job: "daily-cleanup" }); try { const startTime = Date.now(); const result = await runCleanup(); const duration = Date.now() - startTime; logger.info("Cron job completed", { job: "daily-cleanup", duration, result }); } catch (err) { logger.error("Cron job failed", { job: "daily-cleanup", error: err.message, stack: err.stack }); await sendAlert(err); } });

3. Track Job Execution in Database

const cronJobSchema = new mongoose.Schema({ jobName: String, status: { type: String, enum: ["running", "completed", "failed"] }, startTime: Date, endTime: Date, duration: Number, result: mongoose.Schema.Types.Mixed, error: String, errorStack: String }); const CronJob = mongoose.model("CronJob", cronJobSchema); const runCronJob = async (jobName, jobFn) => { const job = await CronJob.create({ jobName, status: "running", startTime: new Date() }); try { const result = await jobFn(); const endTime = new Date(); await CronJob.findByIdAndUpdate(job._id, { status: "completed", endTime, duration: endTime - job.startTime, result }); return result; } catch (err) { const endTime = new Date(); await CronJob.findByIdAndUpdate(job._id, { status: "failed", endTime, duration: endTime - job.startTime, error: err.message, errorStack: err.stack }); throw err; } }; // Usage cron.schedule("0 0 * * *", () => { runCronJob("daily-cleanup", runCleanup).catch(console.error); });

4. Send Alerts on Failure

const sendAlert = async (message) => { // Email alert await sendEmail("[email protected]", "alert", { message }); // Slack alert (webhook) await fetch(process.env.SLACK_WEBHOOK_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: `🚨 Cron Job Alert: ${message}` }) }); // SMS alert (for critical jobs) // await sendSMS("+1234567890", message); };

5. Prevent Overlapping Runs

If a cron job takes longer than the interval, multiple instances may overlap:

const isRunning = {}; const runJob = async (jobName, jobFn) => { if (isRunning[jobName]) { console.log(`Job ${jobName} is already running, skipping`); return; } isRunning[jobName] = true; try { await jobFn(); } finally { isRunning[jobName] = false; } }; cron.schedule("*/15 * * * *", () => { runJob("frequent-task", frequentTask); });

6. Retry Failed Jobs

const runWithRetry = async (jobFn, retries = 3) => { for (let i = 0; i < retries; i++) { try { return await jobFn(); } catch (err) { console.error(`Attempt ${i + 1} failed: ${err.message}`); if (i === retries - 1) throw err; await new Promise(resolve => setTimeout(resolve, 5000 * (i + 1))); } } }; cron.schedule("0 0 * * *", () => { runWithRetry(runCleanup, 3).catch(err => { sendAlert("Daily cleanup failed after 3 retries: " + err.message); }); });

7. Monitor Job Duration

cron.schedule("0 0 * * *", async () => { const startTime = Date.now(); try { await runCleanup(); const duration = Date.now() - startTime; if (duration > 300000) { // 5 minutes logger.warn("Cron job took longer than expected", { duration }); await sendAlert(`Cleanup took ${duration / 1000}s`); } } catch (err) { // ... } });

8. Health Check Endpoint for Cron Jobs

router.get("/health/cron", asyncHandler(async (req, res) => { const last24Hours = new Date(Date.now() - 24 * 60 * 60 * 1000); const jobs = await CronJob.find({ startTime: { $gte: last24Hours } }) .sort({ startTime: -1 }); const summary = jobs.reduce((acc, job) => { acc[job.jobName] = acc[job.jobName] || { lastStatus: job.status, lastRun: job.startTime, failures: 0 }; if (job.status === "failed") acc[job.jobName].failures++; return acc; }, {}); res.json({ data: summary }); }));

The Takeaway

Cron job error handling involves: always using try-catch, logging with timestamps and duration, tracking job execution in a database, sending alerts (email, Slack, SMS) on failure, preventing overlapping runs with a flag, retrying failed jobs with exponential backoff, monitoring job duration for anomalies, and providing a health check endpoint to view job status. Track job name, status, start/end time, duration, result, and error in a CronJob collection.

Always wrap the job logic in try-catch. Log the error with details (message, stack, job name). Send an alert (email, Slack, SMS) to the admin. Track the failure in a CronJob database collection. Consider retrying with exponential backoff for transient errors.

Use a flag: if (isRunning[jobName]) { console.log('Already running, skipping'); return; } Set isRunning[jobName] = true before the job and false in a finally block. This prevents multiple instances from running simultaneously if the job takes longer than the interval.

Track each job execution in a CronJob database collection (jobName, status, startTime, endTime, duration, result, error). Create a /health/cron endpoint that shows the last status and failure count for each job. Set up alerts for failed jobs and long-running jobs.

Implement a retry wrapper that catches errors and retries with exponential backoff (5s, 10s, 20s). Only retry on transient errors (network, database). Don't retry on logic errors. Send an alert if all retries fail. Track retry attempts in the CronJob collection.

Log the job name, start time, end time, duration, result (success or failure), error message and stack trace. Use Winston with JSON format and write to both console and a file (logs/cron.log). Include enough context to debug failures without re-running the job.

Ready to master Node.js completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.