@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
243 lines • 8.97 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 BufferedEntryManagerService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BufferedEntryManagerService = void 0;
const common_1 = require("@nestjs/common");
let BufferedEntryManagerService = BufferedEntryManagerService_1 = class BufferedEntryManagerService {
constructor(config = {}, flushHandler) {
this.flushHandler = flushHandler;
this.logger = new common_1.Logger(BufferedEntryManagerService_1.name);
this.buffer = [];
this.flushTimer = null;
this.isFlushInProgress = false;
this.config = {
enabled: true,
maxBufferSize: 1000,
flushInterval: 5000,
maxBatchSize: 100,
priorityFlushThreshold: 50,
retryAttempts: 3,
retryDelay: 1000,
...config,
};
this.stats = {
currentBufferSize: 0,
totalEntries: 0,
totalFlushes: 0,
totalFailures: 0,
lastFlushTime: 0,
averageFlushSize: 0,
bufferUtilization: 0,
};
if (this.config.enabled) {
this.startFlushTimer();
}
this.logger.debug('Buffered Entry Manager initialized', this.config);
}
async addEntry(entry, priority = 1) {
if (!this.config.enabled) {
try {
await this.flushHandler([entry]);
}
catch (error) {
this.logger.error('Failed to flush entry immediately:', error);
throw error;
}
return;
}
try {
const bufferedEntry = {
entry,
timestamp: Date.now(),
priority,
retryCount: 0,
};
this.buffer.push(bufferedEntry);
this.stats.totalEntries++;
this.stats.currentBufferSize = this.buffer.length;
this.updateBufferUtilization();
if (this.shouldFlushImmediately()) {
await this.flushBuffer();
}
}
catch (error) {
this.logger.error('Failed to add entry to buffer:', error);
throw error;
}
}
async addBatch(entries, priority = 1) {
if (!this.config.enabled) {
try {
await this.flushHandler(entries);
}
catch (error) {
this.logger.error('Failed to flush batch immediately:', error);
throw error;
}
return;
}
try {
const timestamp = Date.now();
const bufferedEntries = entries.map((entry) => ({
entry,
timestamp,
priority,
retryCount: 0,
}));
this.buffer.push(...bufferedEntries);
this.stats.totalEntries += entries.length;
this.stats.currentBufferSize = this.buffer.length;
this.updateBufferUtilization();
if (this.shouldFlushImmediately()) {
await this.flushBuffer();
}
}
catch (error) {
this.logger.error('Failed to add batch to buffer:', error);
throw error;
}
}
async flush() {
await this.flushBuffer();
}
getStats() {
return { ...this.stats };
}
getBufferSize() {
return this.buffer.length;
}
isHealthy() {
return this.stats.bufferUtilization < 0.9 && !this.isFlushInProgress;
}
clearBuffer() {
const clearedCount = this.buffer.length;
this.buffer.splice(0);
this.stats.currentBufferSize = 0;
this.updateBufferUtilization();
this.logger.warn(`Buffer cleared: ${clearedCount} entries removed`);
}
onDestroy() {
this.shutdown();
}
async shutdown() {
if (this.flushTimer) {
clearInterval(this.flushTimer);
this.flushTimer = null;
}
if (this.buffer.length > 0) {
this.logger.debug(`Flushing ${this.buffer.length} remaining entries on shutdown`);
await this.flushBuffer();
}
this.logger.debug('Buffered Entry Manager shutdown complete');
}
shouldFlushImmediately() {
if (this.buffer.length >= this.config.maxBufferSize) {
return true;
}
const highPriorityCount = this.buffer.filter((entry) => entry.priority >= 5).length;
if (highPriorityCount >= this.config.priorityFlushThreshold) {
return true;
}
const hasCriticalEntries = this.buffer.some((entry) => entry.priority >= 8);
if (hasCriticalEntries) {
return true;
}
return false;
}
startFlushTimer() {
this.flushTimer = setInterval(async () => {
if (this.buffer.length > 0 && !this.isFlushInProgress) {
await this.flushBuffer();
}
}, this.config.flushInterval);
}
async flushBuffer() {
if (this.isFlushInProgress || this.buffer.length === 0) {
return;
}
this.isFlushInProgress = true;
const startTime = Date.now();
try {
this.buffer.sort((a, b) => {
if (a.priority !== b.priority) {
return b.priority - a.priority;
}
return a.timestamp - b.timestamp;
});
while (this.buffer.length > 0) {
const batchSize = Math.min(this.config.maxBatchSize, this.buffer.length);
const batch = this.buffer.splice(0, batchSize);
await this.processBatch(batch);
}
this.stats.totalFlushes++;
this.stats.lastFlushTime = Date.now();
this.stats.currentBufferSize = this.buffer.length;
this.updateBufferUtilization();
this.updateAverageFlushSize();
const duration = Date.now() - startTime;
this.logger.debug(`Buffer flushed successfully in ${duration}ms`);
}
catch (error) {
this.logger.error('Buffer flush failed:', error);
this.stats.totalFailures++;
}
finally {
this.isFlushInProgress = false;
}
}
async processBatch(batch) {
const entries = batch.map((bufferedEntry) => bufferedEntry.entry);
try {
await this.flushHandler(entries);
}
catch (error) {
await this.handleFailedBatch(batch, error);
}
}
async handleFailedBatch(batch, error) {
this.logger.warn(`Batch flush failed, attempting retry:`, error.message);
const retryableBatch = [];
for (const bufferedEntry of batch) {
if (bufferedEntry.retryCount < this.config.retryAttempts) {
bufferedEntry.retryCount++;
retryableBatch.push(bufferedEntry);
}
else {
this.logger.error(`Entry failed after ${this.config.retryAttempts} attempts, dropping:`, {
entryId: bufferedEntry.entry.id,
type: bufferedEntry.entry.type,
});
}
}
if (retryableBatch.length > 0) {
await this.sleep(this.config.retryDelay * retryableBatch[0].retryCount);
this.buffer.unshift(...retryableBatch);
}
}
updateBufferUtilization() {
this.stats.bufferUtilization = this.buffer.length / this.config.maxBufferSize;
}
updateAverageFlushSize() {
if (this.stats.totalFlushes > 0) {
this.stats.averageFlushSize = this.stats.totalEntries / this.stats.totalFlushes;
}
}
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
};
exports.BufferedEntryManagerService = BufferedEntryManagerService;
exports.BufferedEntryManagerService = BufferedEntryManagerService = BufferedEntryManagerService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [Object, Function])
], BufferedEntryManagerService);
//# sourceMappingURL=buffered-entry-manager.service.js.map