Cron Monitoring Setup
Monitor scheduled jobs and background tasks with check-ins, timeout detection, and failure alerts.
What is Cron Monitoring?#
Cron monitoring tracks the execution of scheduled tasks, background jobs, and recurring processes in your application. It detects three types of failures:
- Missed check-ins -- your job did not run when expected
- Timeouts -- your job started but did not complete within the expected time
- Failures -- your job ran but reported an error
Unlike traditional uptime monitoring that checks external endpoints, cron monitoring works from inside your application -- your code reports check-ins to JustAnalytics, and the system alerts you when something goes wrong.
How It Works#
Cron monitoring uses a check-in flow:
Your Job Starts → Send "in_progress" check-in → Job runs → Send "ok" or "error" check-in
If JustAnalytics does not receive an in_progress check-in within the expected schedule window, it marks the job as missed. If it receives in_progress but no completion (ok or error) within the timeout period, it marks the job as timed out.
Prerequisites#
Install the Node.js SDK:
npm install @justanalyticsapp/node
Initialize the SDK in your application:
import JA from '@justanalyticsapp/node';
JA.init({
siteId: 'YOUR_SITE_ID',
apiKey: 'YOUR_API_KEY',
serviceName: 'my-worker',
});
Creating a Cron Monitor#
In the Dashboard#
- Navigate to Monitoring > Cron Monitors
- Click Create Monitor
- Configure the monitor:
| Field | Description | Example |
|-------|-------------|---------|
| Name | Descriptive name for the job | daily-report-generation |
| Slug | URL-safe identifier used in API calls | daily-report-generation |
| Schedule | Cron expression or interval | 0 2 * * * (daily at 2am) |
| Timezone | Timezone for the schedule | America/New_York |
| Grace Period | Minutes to wait before marking as missed | 5 |
| Timeout | Minutes before marking an in-progress job as timed out | 30 |
| Alert Channels | Where to send failure notifications | Email, Slack |
- Click Save and copy the generated monitor slug
Cron Expression Reference#
| Expression | Meaning |
|------------|---------|
| * * * * * | Every minute |
| */5 * * * * | Every 5 minutes |
| 0 * * * * | Every hour |
| 0 */6 * * * | Every 6 hours |
| 0 2 * * * | Daily at 2:00 AM |
| 0 9 * * 1-5 | Weekdays at 9:00 AM |
| 0 0 * * 0 | Weekly on Sunday at midnight |
| 0 0 1 * * | Monthly on the 1st at midnight |
Sending Check-Ins#
Basic Check-In Flow#
import JA from '@justanalyticsapp/node';
async function dailyReportJob() {
// Signal that the job has started
const checkInId = await JA.cronCheckIn('daily-report-generation', {
status: 'in_progress',
});
try {
// Your job logic
const report = await generateDailyReport();
await sendReportEmail(report);
// Signal successful completion
await JA.cronCheckIn('daily-report-generation', {
status: 'ok',
checkInId, // Links to the in_progress check-in
duration: Date.now() - startTime,
});
} catch (error) {
// Signal failure
await JA.cronCheckIn('daily-report-generation', {
status: 'error',
checkInId,
message: error.message,
});
throw error; // Re-throw so your job runner knows it failed
}
}
Simplified Wrapper#
Use JA.withCronMonitor() for a cleaner syntax that handles the in_progress/ok/error flow automatically:
import JA from '@justanalyticsapp/node';
const result = await JA.withCronMonitor('daily-report-generation', async () => {
const report = await generateDailyReport();
await sendReportEmail(report);
return report;
});
// If the function throws, status is set to 'error' automatically
// If it completes, status is set to 'ok' automatically
// Duration is measured automatically
Check-In API Reference#
JA.cronCheckIn(monitorSlug: string, options: {
status: 'in_progress' | 'ok' | 'error';
checkInId?: string; // Required for 'ok' and 'error' to link to 'in_progress'
duration?: number; // Duration in milliseconds
message?: string; // Optional message (e.g., error message or summary)
environment?: string; // Override the default environment
}): Promise<string>; // Returns checkInId
Grace Periods and Timeouts#
Grace Period#
The grace period is how long JustAnalytics waits after the expected run time before marking the job as missed. This accounts for slight delays in job scheduling.
Schedule: 0 2 * * * (daily at 2:00 AM)
Grace Period: 5 minutes
Expected check-in window: 2:00 AM - 2:05 AM
If no check-in by 2:05 AM → marked as MISSED
Set a longer grace period for jobs that may be delayed by queue congestion or server load:
- Short jobs (< 1 min): 2-5 minute grace period
- Medium jobs (1-10 min): 5-15 minute grace period
- Long-running jobs (> 10 min): 15-30 minute grace period
- Jobs in autoscaling environments: 10-30 minute grace period
Timeout#
The timeout is how long JustAnalytics waits after receiving an in_progress check-in before marking the job as timed out.
In-progress received at: 2:01 AM
Timeout: 30 minutes
If no 'ok' or 'error' by 2:31 AM → marked as TIMED OUT
Set the timeout based on your job's expected maximum duration plus a buffer:
- If the job normally takes 5 minutes, set timeout to 15 minutes
- If the job normally takes 30 minutes, set timeout to 60 minutes
- If the job has variable duration, use the 99th percentile plus 50%
Alert Rules for Cron Monitors#
Default Alerts#
By default, cron monitors send alerts for:
- Missed check-in -- the job did not start when expected
- Timeout -- the job started but did not complete
- Error -- the job reported a failure
Custom Alert Rules#
Create custom rules for more nuanced alerting:
{
"name": "Report Job Consecutive Failures",
"monitor": "daily-report-generation",
"condition": "consecutive_failures",
"threshold": 3,
"channels": ["slack", "pagerduty"],
"message": "Daily report has failed 3 times in a row"
}
{
"name": "Slow Sync Job",
"monitor": "hourly-data-sync",
"condition": "duration_exceeds",
"threshold_ms": 300000,
"channels": ["email"],
"message": "Hourly sync took longer than 5 minutes"
}
Alert Channels#
Cron monitor alerts can be sent to:
- Email -- sent to site owner and configured team members
- Slack -- posted to a configured Slack channel via webhook
- Webhook -- sent as a POST request to any URL
- PagerDuty -- creates an incident via PagerDuty integration
Configure channels in Settings > Notification Channels.
Code Examples for Common Patterns#
Daily Database Cleanup#
import JA from '@justanalyticsapp/node';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Runs daily at 3:00 AM
async function cleanupOldRecords() {
await JA.withCronMonitor('daily-db-cleanup', async () => {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 90);
const deleted = await prisma.tempFile.deleteMany({
where: { createdAt: { lt: cutoff } },
});
JA.logger.info('Database cleanup completed', {
deletedRecords: deleted.count,
cutoffDate: cutoff.toISOString(),
});
return { deleted: deleted.count };
});
}
Hourly Data Sync#
import JA from '@justanalyticsapp/node';
// Runs every hour
async function syncExternalData() {
await JA.withCronMonitor('hourly-data-sync', async () => {
const sources = ['inventory-api', 'pricing-api', 'shipping-api'];
const results = [];
for (const source of sources) {
const data = await fetchFromSource(source);
await updateLocalDatabase(source, data);
results.push({ source, records: data.length });
}
JA.logger.info('Data sync completed', { results });
return results;
});
}
Weekly Report Generation#
import JA from '@justanalyticsapp/node';
// Runs every Monday at 8:00 AM
async function generateWeeklyReport() {
await JA.withCronMonitor('weekly-report', async () => {
const report = await buildReport({
startDate: getLastMonday(),
endDate: getLastSunday(),
metrics: ['revenue', 'orders', 'new_users', 'churn'],
});
await sendToSlack(report.summary);
await sendByEmail(report.full, ['team@company.com']);
await archiveReport(report);
JA.logger.info('Weekly report sent', {
period: `${report.startDate} to ${report.endDate}`,
recipients: report.recipientCount,
});
});
}
Queue Worker Health Check#
For long-running queue workers that process jobs continuously, use periodic heartbeat check-ins:
import JA from '@justanalyticsapp/node';
// Worker that processes jobs from a queue
async function startQueueWorker() {
const HEARTBEAT_INTERVAL = 5 * 60 * 1000; // 5 minutes
let jobsProcessed = 0;
let lastHeartbeat = Date.now();
while (true) {
const job = await queue.dequeue();
if (job) {
await processJob(job);
jobsProcessed++;
}
// Send heartbeat every 5 minutes
if (Date.now() - lastHeartbeat > HEARTBEAT_INTERVAL) {
await JA.cronCheckIn('queue-worker-heartbeat', {
status: 'ok',
message: `Processed ${jobsProcessed} jobs since last heartbeat`,
});
jobsProcessed = 0;
lastHeartbeat = Date.now();
}
}
}
Express.js Cron with node-cron#
import JA from '@justanalyticsapp/node';
import cron from 'node-cron';
// Schedule a job using node-cron
cron.schedule('0 */6 * * *', async () => {
await JA.withCronMonitor('6h-cache-refresh', async () => {
await refreshProductCache();
await refreshCategoryCache();
await refreshSearchIndex();
JA.logger.info('Cache refresh completed');
});
}, {
timezone: 'America/New_York',
});
Bull/BullMQ Recurring Job#
import JA from '@justanalyticsapp/node';
import { Queue, Worker } from 'bullmq';
const queue = new Queue('scheduled-tasks');
// Add a recurring job
await queue.add('daily-digest', {}, {
repeat: { cron: '0 9 * * *' },
});
// Worker with cron monitoring
const worker = new Worker('scheduled-tasks', async (job) => {
if (job.name === 'daily-digest') {
await JA.withCronMonitor('daily-digest-email', async () => {
const users = await getActiveUsers();
for (const user of users) {
const digest = await buildDigest(user);
await sendDigestEmail(user.email, digest);
}
JA.logger.info('Daily digest sent', { userCount: users.length });
});
}
});
Viewing Cron Monitor Status#
Dashboard Overview#
Navigate to Monitoring > Cron Monitors to see all your monitors:
| Monitor | Schedule | Last Check-In | Status | Duration | Next Expected |
|---------|----------|---------------|--------|----------|---------------|
| daily-report | 0 2 * * * | 2:01 AM | OK | 4m 23s | Tomorrow 2:00 AM |
| hourly-sync | 0 * * * * | 11:00 AM | OK | 1m 12s | 12:00 PM |
| weekly-report | 0 8 * * 1 | Mon 8:00 AM | OK | 12m 45s | Next Mon 8:00 AM |
| queue-worker | Every 5m | 11:55 AM | OK | - | 12:00 PM |
| nightly-cleanup | 0 3 * * * | 3:00 AM | ERROR | 0m 8s | Tomorrow 3:00 AM |
Status Colors#
| Status | Color | Meaning | |--------|-------|---------| | OK | Green | Last check-in completed successfully | | In Progress | Blue | Job is currently running | | Error | Red | Last check-in reported an error | | Missed | Orange | Expected check-in was not received | | Timed Out | Yellow | Job started but did not complete | | Disabled | Gray | Monitor is paused |
Check-In History#
Click any monitor to see its check-in history:
- Timeline of check-ins with status and duration
- Duration trend chart (line chart showing job duration over time)
- Success/failure rate over time
- Error messages from failed runs
- Link to associated logs and traces (if SDK logging is enabled)
Best Practices#
- Use
withCronMonitorwrapper -- it handles the in_progress/ok/error flow and catches exceptions automatically - Set realistic timeouts -- base them on observed job duration, not best-case
- Log within monitored jobs -- use
JA.loggerinside your jobs so logs are correlated with check-ins - Monitor queue workers -- use heartbeat check-ins for long-running processes
- Name monitors descriptively -- use names like
daily-report-generationnotcron-1 - Test alert channels -- send a test alert when setting up a new monitor to verify delivery
- Review duration trends -- gradually increasing duration may indicate a growing dataset or performance issue
Troubleshooting#
Check-In Not Received#
- Verify the monitor slug matches exactly (case-sensitive)
- Check that the SDK is initialized with the correct
siteIdandapiKey - Look for network errors in your application logs
- Verify the SDK can reach
https://justanalytics.app/api/ingest/cron
False Missed Alerts#
- Increase the grace period to account for scheduling delays
- Check if the server's clock is synchronized (NTP)
- Verify the timezone setting matches your cron scheduler's timezone
False Timeout Alerts#
- Increase the timeout to account for worst-case job duration
- Check if the job is actually hanging (review application logs)
- Ensure the
okorerrorcheck-in is sent even when the job exits unexpectedly (usewithCronMonitoror afinallyblock)
Next Steps#
- Alert Rules -- configure alert rules for cron failures
- Notification Channels -- set up email, Slack, and webhook notifications
- Structured Logging -- add logging to your cron jobs
- Traces -- trace the operations within your cron jobs