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);
}
});