Skip to main content

Events

Chronos provides a comprehensive event system to monitor job execution and scheduler status in real-time.

Scheduler Events

Events emitted by the Chronos scheduler instance.

ready

Emitted when Chronos successfully connects to MongoDB and creates indexes.

scheduler.on('ready', () => {
console.log('Chronos is ready to process jobs');
});

await scheduler.start(); // This will wait for 'ready' internally

error

Emitted when Chronos encounters a MongoDB connection error.

scheduler.on('error', (error) => {
console.error('Scheduler error:', error);
// Implement your error handling logic
});

Job Lifecycle Events

Monitor individual job execution with these events.

start

Emitted when any job starts execution.

scheduler.on('start', (job) => {
console.log(`Job started: ${job.attrs.name}`);
console.log(`Job ID: ${job.attrs._id}`);
console.log(`Started at: ${new Date()}`);
});

complete

Emitted when any job finishes (regardless of success or failure).

scheduler.on('complete', (job) => {
const duration = Date.now() - job.attrs.lockedAt.getTime();
console.log(`Job completed: ${job.attrs.name}`);
console.log(`Duration: ${duration}ms`);
});

success

Emitted when any job completes successfully.

scheduler.on('success', (job) => {
console.log(`Job succeeded: ${job.attrs.name}`);

// Access job result if shouldSaveResult was enabled
if (job.attrs.result) {
console.log('Job result:', job.attrs.result);
}
});

fail

Emitted when any job fails with an error.

scheduler.on('fail', (err, job) => {
console.error(`Job failed: ${job.attrs.name}`);
console.error('Error:', err.message);
console.error('Failure reason:', job.attrs.failReason);

// Implement retry logic, alerting, etc.
});

Job-Specific Events

Listen to events for specific job types by appending the job name to the event.

start:{jobName}

scheduler.on('start:send email', (job) => {
const { to, subject } = job.attrs.data;
console.log(`Starting to send email to ${to}: ${subject}`);
});

complete:{jobName}

scheduler.on('complete:generate report', (job) => {
console.log('Report generation finished');
});

success:{jobName}

scheduler.on('success:process payment', (job) => {
const { orderId, amount } = job.attrs.data;
console.log(`Payment processed successfully: Order ${orderId}, Amount: $${amount}`);

// Trigger follow-up actions
scheduler.now('send payment confirmation', { orderId });
});

fail:{jobName}

scheduler.on('fail:api sync', (err, job) => {
const { endpoint } = job.attrs.data;
console.error(`API sync failed for ${endpoint}:`, err.message);

// Implement specific retry logic for API failures
if (err.code === 'NETWORK_ERROR') {
scheduler.schedule('in 5 minutes', 'api sync', job.attrs.data);
}
});

Comprehensive Event Monitoring

Basic Monitoring Setup

const scheduler = new Chronos({
db: { address: 'mongodb://localhost:27017/scheduler' }
});

// Scheduler lifecycle
scheduler.on('ready', () => {
console.log('✅ Chronos ready');
});

scheduler.on('error', (err) => {
console.error('❌ Scheduler error:', err);
});

// Job monitoring
scheduler.on('start', (job) => {
console.log(`🚀 Starting: ${job.attrs.name}`);
});

scheduler.on('success', (job) => {
console.log(`✅ Success: ${job.attrs.name}`);
});

scheduler.on('fail', (err, job) => {
console.error(`❌ Failed: ${job.attrs.name} - ${err.message}`);
});

scheduler.on('complete', (job) => {
const duration = Date.now() - job.attrs.lockedAt.getTime();
console.log(`⏱️ Completed: ${job.attrs.name} (${duration}ms)`);
});

Advanced Monitoring with Metrics

class JobMetrics {
constructor() {
this.stats = {
started: 0,
completed: 0,
failed: 0,
totalDuration: 0
};
}

setupMonitoring(scheduler) {
scheduler.on('start', (job) => {
this.stats.started++;
job._startTime = Date.now();
});

scheduler.on('complete', (job) => {
this.stats.completed++;
if (job._startTime) {
this.stats.totalDuration += Date.now() - job._startTime;
}
});

scheduler.on('fail', (err, job) => {
this.stats.failed++;
});
}

getStats() {
const avgDuration = this.stats.completed > 0
? this.stats.totalDuration / this.stats.completed
: 0;

return {
...this.stats,
successRate: this.stats.completed > 0
? (this.stats.completed / (this.stats.completed + this.stats.failed)) * 100
: 0,
avgDuration: Math.round(avgDuration)
};
}
}

const metrics = new JobMetrics();
metrics.setupMonitoring(scheduler);

// Log metrics every minute
setInterval(() => {
console.log('Job Metrics:', metrics.getStats());
}, 60000);

Event-Driven Job Chaining

Use events to create complex job workflows:

// Define related jobs
scheduler.define('process order', async (job) => {
const { orderId } = job.attrs.data;
// Process the order
await processOrder(orderId);
});

scheduler.define('send confirmation', async (job) => {
const { orderId, email } = job.attrs.data;
// Send confirmation email
await sendConfirmationEmail(email, orderId);
});

scheduler.define('update inventory', async (job) => {
const { items } = job.attrs.data;
// Update inventory
await updateInventory(items);
});

// Chain jobs using events
scheduler.on('success:process order', (job) => {
const { orderId, customerEmail, items } = job.attrs.data;

// Schedule follow-up jobs
scheduler.now('send confirmation', {
orderId,
email: customerEmail
});

scheduler.now('update inventory', {
items
});
});

scheduler.on('fail:process order', (err, job) => {
const { orderId } = job.attrs.data;

// Schedule retry or error handling
scheduler.schedule('in 30 minutes', 'process order', job.attrs.data);

// Notify administrators
scheduler.now('send admin alert', {
type: 'order_processing_failed',
orderId,
error: err.message
});
});

Logging Integration

Winston Integration

import winston from 'winston';

const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'jobs.log' })
]
});

scheduler.on('start', (job) => {
logger.info('Job started', {
jobName: job.attrs.name,
jobId: job.attrs._id,
data: job.attrs.data
});
});

scheduler.on('success', (job) => {
logger.info('Job succeeded', {
jobName: job.attrs.name,
jobId: job.attrs._id,
duration: Date.now() - job.attrs.lockedAt.getTime()
});
});

scheduler.on('fail', (err, job) => {
logger.error('Job failed', {
jobName: job.attrs.name,
jobId: job.attrs._id,
error: err.message,
stack: err.stack,
data: job.attrs.data
});
});

Application Performance Monitoring (APM)

// Example with hypothetical APM service
scheduler.on('start', (job) => {
const transaction = apm.startTransaction(`job:${job.attrs.name}`, 'job');
job._apmTransaction = transaction;
});

scheduler.on('success', (job) => {
if (job._apmTransaction) {
job._apmTransaction.result = 'success';
job._apmTransaction.end();
}
});

scheduler.on('fail', (err, job) => {
if (job._apmTransaction) {
job._apmTransaction.result = 'error';
job._apmTransaction.captureError(err);
job._apmTransaction.end();
}
});

Real-time Notifications

WebSocket Notifications

import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

// Broadcast job events to connected clients
scheduler.on('start', (job) => {
broadcast({
type: 'job_start',
job: {
name: job.attrs.name,
id: job.attrs._id,
data: job.attrs.data
}
});
});

scheduler.on('complete', (job) => {
broadcast({
type: 'job_complete',
job: {
name: job.attrs.name,
id: job.attrs._id,
success: !job.attrs.failedAt
}
});
});

function broadcast(message) {
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message));
}
});
}

Event Best Practices

1. Keep Event Handlers Fast

// Good: Fast, non-blocking handler
scheduler.on('success:send email', (job) => {
console.log(`Email sent to ${job.attrs.data.to}`);
});

// Avoid: Slow, blocking operations in handlers
scheduler.on('success:send email', async (job) => {
// Don't do this - it blocks other events
await heavyDatabaseOperation();
});

2. Use Specific Events When Possible

// Specific - better performance
scheduler.on('success:payment processing', (job) => {
// Handle payment success
});

// Generic - handles all jobs
scheduler.on('success', (job) => {
if (job.attrs.name === 'payment processing') {
// Handle payment success
}
});

3. Handle Errors in Event Listeners

scheduler.on('fail', (err, job) => {
try {
// Your error handling logic
notifyAdmins(err, job);
} catch (handlerError) {
console.error('Error in event handler:', handlerError);
}
});