@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
323 lines • 13.4 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var BullAdapterService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BullAdapterService = void 0;
const common_1 = require("@nestjs/common");
const job_watcher_service_1 = require("./job-watcher.service");
const core_1 = require("@nestjs/core");
let BullAdapterService = BullAdapterService_1 = class BullAdapterService {
constructor(jobWatcherService, moduleRef) {
this.jobWatcherService = jobWatcherService;
this.moduleRef = moduleRef;
this.logger = new common_1.Logger(BullAdapterService_1.name);
this.registeredQueues = new Map();
this.eventListeners = new Map();
}
async onModuleInit() {
await this.discoverQueues();
}
async discoverQueues() {
try {
const bullQueues = await this.findBullQueues();
for (const queue of bullQueues) {
this.registerQueue(queue);
}
this.logger.log(`Discovered and registered ${bullQueues.length} Bull queues`);
}
catch (error) {
this.logger.warn('Failed to auto-discover Bull queues:', error.message);
}
}
async findBullQueues() {
const queues = [];
try {
const bullQueues = await this.findQueuesFromBull();
queues.push(...bullQueues);
const bullmqQueues = await this.findQueuesFromBullMQ();
queues.push(...bullmqQueues);
}
catch (error) {
this.logger.debug('No Bull queues found via auto-discovery');
}
return queues;
}
async findQueuesFromBull() {
const queues = [];
try {
const bullModule = await this.moduleRef.get('BullModule', { strict: false });
if (bullModule && bullModule.queues) {
for (const [name, queue] of Object.entries(bullModule.queues)) {
queues.push(queue);
}
}
}
catch (error) {
}
return queues;
}
async findQueuesFromBullMQ() {
const queues = [];
try {
const bullMQModule = await this.moduleRef.get('BullMQModule', { strict: false });
if (bullMQModule && bullMQModule.queues) {
for (const [name, queue] of Object.entries(bullMQModule.queues)) {
queues.push(queue);
}
}
}
catch (error) {
}
return queues;
}
registerQueue(queue) {
if (!queue || !queue.name) {
this.logger.warn('Invalid queue provided for registration');
return;
}
if (this.registeredQueues.has(queue.name)) {
this.logger.warn(`Queue ${queue.name} is already registered`);
return;
}
this.registeredQueues.set(queue.name, queue);
this.setupQueueEventListeners(queue);
this.logger.log(`Registered Bull queue: ${queue.name}`);
}
setupQueueEventListeners(queue) {
const listeners = new Map();
const onJobActive = (job) => this.handleJobActive(job, queue);
const onJobCompleted = (job, result) => this.handleJobCompleted(job, result, queue);
const onJobFailed = (job, error) => this.handleJobFailed(job, error, queue);
const onJobProgress = (job, progress) => this.handleJobProgress(job, progress, queue);
const onJobStalled = (job) => this.handleJobStalled(job, queue);
const onJobWaiting = (job) => this.handleJobWaiting(job, queue);
const onJobDelayed = (job) => this.handleJobDelayed(job, queue);
const onJobPaused = (job) => this.handleJobPaused(job, queue);
queue.on('active', onJobActive);
queue.on('completed', onJobCompleted);
queue.on('failed', onJobFailed);
queue.on('progress', onJobProgress);
queue.on('stalled', onJobStalled);
queue.on('waiting', onJobWaiting);
queue.on('delayed', onJobDelayed);
queue.on('paused', onJobPaused);
listeners.set('active', onJobActive);
listeners.set('completed', onJobCompleted);
listeners.set('failed', onJobFailed);
listeners.set('progress', onJobProgress);
listeners.set('stalled', onJobStalled);
listeners.set('waiting', onJobWaiting);
listeners.set('delayed', onJobDelayed);
listeners.set('paused', onJobPaused);
this.eventListeners.set(queue.name, listeners);
}
handleJobActive(job, queue) {
const context = this.createJobContext(job, queue, job_watcher_service_1.JobStatus.ACTIVE);
context.startTime = new Date();
this.jobWatcherService.trackJob(context);
}
handleJobCompleted(job, result, queue) {
const context = this.createJobContext(job, queue, job_watcher_service_1.JobStatus.COMPLETED);
context.result = result;
context.endTime = new Date();
if (job.processedOn && job.timestamp) {
context.duration = job.finishedOn - job.processedOn;
context.performance = {
...context.performance,
queueWaitTime: job.processedOn - job.timestamp,
executionTime: context.duration,
};
}
this.jobWatcherService.trackJob(context);
}
handleJobFailed(job, error, queue) {
const context = this.createJobContext(job, queue, job_watcher_service_1.JobStatus.FAILED);
context.error = {
message: error.message,
stack: error.stack,
name: error.name,
failedReason: job.failedReason,
};
context.endTime = new Date();
if (job.processedOn && job.timestamp) {
context.duration = job.finishedOn - job.processedOn;
context.performance = {
...context.performance,
queueWaitTime: job.processedOn - job.timestamp,
executionTime: context.duration,
};
}
this.jobWatcherService.trackJob(context);
}
handleJobProgress(job, progress, queue) {
const context = this.createJobContext(job, queue, job_watcher_service_1.JobStatus.ACTIVE);
context.progress = progress;
this.jobWatcherService.trackJob(context);
}
handleJobStalled(job, queue) {
const context = this.createJobContext(job, queue, job_watcher_service_1.JobStatus.STALLED);
this.jobWatcherService.trackJob(context);
}
handleJobWaiting(job, queue) {
const context = this.createJobContext(job, queue, job_watcher_service_1.JobStatus.WAITING);
this.jobWatcherService.trackJob(context);
}
handleJobDelayed(job, queue) {
const context = this.createJobContext(job, queue, job_watcher_service_1.JobStatus.DELAYED);
context.delay = job.delay;
this.jobWatcherService.trackJob(context);
}
handleJobPaused(job, queue) {
const context = this.createJobContext(job, queue, job_watcher_service_1.JobStatus.PAUSED);
this.jobWatcherService.trackJob(context);
}
createJobContext(job, queue, status) {
return {
id: `${queue.name}_${job.id}_${Date.now()}`,
jobId: String(job.id),
queueName: queue.name,
jobName: job.name,
timestamp: new Date(),
status,
priority: job.opts?.priority || 0,
data: job.data,
options: job.opts,
attempts: job.attemptsMade,
maxAttempts: job.opts?.attempts || 3,
delay: job.delay,
progress: job.progress,
performance: {
memoryUsage: process.memoryUsage().heapUsed,
cpuUsage: process.cpuUsage().user + process.cpuUsage().system,
},
traceId: job.data?.traceId || job.opts?.traceId,
requestId: job.data?.requestId || job.opts?.requestId,
userId: job.data?.userId || job.opts?.userId,
sessionId: job.data?.sessionId || job.opts?.sessionId,
};
}
async updateQueueStats() {
for (const [name, queue] of this.registeredQueues) {
try {
const stats = await queue.getJobCounts();
const context = {
id: `queue_stats_${name}_${Date.now()}`,
jobId: 'queue_stats',
queueName: name,
jobName: 'queue_stats',
timestamp: new Date(),
status: job_watcher_service_1.JobStatus.ACTIVE,
priority: 0,
attempts: 0,
maxAttempts: 0,
queueStats: stats,
};
this.jobWatcherService.trackJob(context);
}
catch (error) {
this.logger.warn(`Failed to get stats for queue ${name}:`, error.message);
}
}
}
unregisterQueue(queueName) {
const queue = this.registeredQueues.get(queueName);
if (!queue) {
this.logger.warn(`Queue ${queueName} is not registered`);
return;
}
const listeners = this.eventListeners.get(queueName);
if (listeners) {
for (const [event, listener] of listeners) {
queue.off(event, listener);
}
this.eventListeners.delete(queueName);
}
this.registeredQueues.delete(queueName);
this.logger.log(`Unregistered Bull queue: ${queueName}`);
}
getRegisteredQueues() {
return Array.from(this.registeredQueues.keys());
}
isQueueRegistered(queueName) {
return this.registeredQueues.has(queueName);
}
async getQueueMetrics(queueName) {
const queue = this.registeredQueues.get(queueName);
if (!queue) {
throw new Error(`Queue ${queueName} is not registered`);
}
try {
const [waiting, active, completed, failed, delayed, paused] = await Promise.all([
queue.waiting ? queue.waiting() : queue.getWaiting(),
queue.active ? queue.active() : queue.getActive(),
queue.completed ? queue.completed() : queue.getCompleted(),
queue.failed ? queue.failed() : queue.getFailed(),
queue.delayed ? queue.delayed() : queue.getDelayed(),
queue.paused ? queue.paused() : queue.getPaused(),
]);
return {
queueName,
counts: {
waiting: waiting.length,
active: active.length,
completed: completed.length,
failed: failed.length,
delayed: delayed.length,
paused: paused.length,
},
jobs: {
waiting: waiting.map(job => this.serializeJob(job)),
active: active.map(job => this.serializeJob(job)),
completed: completed.slice(-10).map(job => this.serializeJob(job)),
failed: failed.slice(-10).map(job => this.serializeJob(job)),
delayed: delayed.map(job => this.serializeJob(job)),
paused: paused.map(job => this.serializeJob(job)),
},
};
}
catch (error) {
this.logger.error(`Failed to get metrics for queue ${queueName}:`, error);
throw error;
}
}
serializeJob(job) {
return {
id: job.id,
name: job.name,
data: job.data,
progress: job.progress,
attemptsMade: job.attemptsMade,
delay: job.delay,
timestamp: job.timestamp,
processedOn: job.processedOn,
finishedOn: job.finishedOn,
};
}
onDestroy() {
for (const [queueName, listeners] of this.eventListeners) {
const queue = this.registeredQueues.get(queueName);
if (queue) {
for (const [event, listener] of listeners) {
queue.off(event, listener);
}
}
}
this.eventListeners.clear();
this.registeredQueues.clear();
}
};
exports.BullAdapterService = BullAdapterService;
exports.BullAdapterService = BullAdapterService = BullAdapterService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [job_watcher_service_1.JobWatcherService,
core_1.ModuleRef])
], BullAdapterService);
//# sourceMappingURL=bull-adapter.service.js.map