UNPKG

@ahmedhegazee/nestjs-telescope

Version:

Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling

386 lines 16 kB
"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 __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var ExceptionWatcherFilter_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExceptionWatcherFilter = exports.ErrorSeverity = exports.ErrorCategory = exports.ErrorClassificationType = void 0; const common_1 = require("@nestjs/common"); const exception_watcher_service_1 = require("./exception-watcher.service"); var ErrorClassificationType; (function (ErrorClassificationType) { ErrorClassificationType["HTTP"] = "http"; ErrorClassificationType["VALIDATION"] = "validation"; ErrorClassificationType["AUTHENTICATION"] = "authentication"; ErrorClassificationType["AUTHORIZATION"] = "authorization"; ErrorClassificationType["DATABASE"] = "database"; ErrorClassificationType["NETWORK"] = "network"; ErrorClassificationType["BUSINESS_LOGIC"] = "business_logic"; ErrorClassificationType["SYSTEM"] = "system"; ErrorClassificationType["UNKNOWN"] = "unknown"; })(ErrorClassificationType || (exports.ErrorClassificationType = ErrorClassificationType = {})); var ErrorCategory; (function (ErrorCategory) { ErrorCategory["CLIENT_ERROR"] = "client_error"; ErrorCategory["SERVER_ERROR"] = "server_error"; ErrorCategory["NETWORK_ERROR"] = "network_error"; ErrorCategory["DATABASE_ERROR"] = "database_error"; ErrorCategory["VALIDATION_ERROR"] = "validation_error"; ErrorCategory["AUTHENTICATION_ERROR"] = "authentication_error"; ErrorCategory["AUTHORIZATION_ERROR"] = "authorization_error"; ErrorCategory["BUSINESS_ERROR"] = "business_error"; ErrorCategory["SYSTEM_ERROR"] = "system_error"; })(ErrorCategory || (exports.ErrorCategory = ErrorCategory = {})); var ErrorSeverity; (function (ErrorSeverity) { ErrorSeverity["LOW"] = "low"; ErrorSeverity["MEDIUM"] = "medium"; ErrorSeverity["HIGH"] = "high"; ErrorSeverity["CRITICAL"] = "critical"; })(ErrorSeverity || (exports.ErrorSeverity = ErrorSeverity = {})); let ExceptionWatcherFilter = ExceptionWatcherFilter_1 = class ExceptionWatcherFilter { constructor(exceptionWatcherService, config) { this.exceptionWatcherService = exceptionWatcherService; this.config = config; this.logger = new common_1.Logger(ExceptionWatcherFilter_1.name); } catch(exception, host) { if (!this.config.enabled) { return; } try { const context = this.extractContext(exception, host); if (this.shouldExcludeError(exception, context)) { return; } if (Math.random() * 100 > this.config.sampleRate) { return; } this.exceptionWatcherService.trackException(context); if (host.getType() === 'http') { this.sendHttpResponse(exception, host, context); } } catch (error) { this.logger.error('Failed to track exception:', error); } } extractContext(exception, host) { const context = { id: this.generateExceptionId(), timestamp: new Date(), error: exception, errorType: exception.constructor.name, errorMessage: exception.message || 'Unknown error', errorCode: exception.code || exception.status }; if (host.getType() === 'http') { this.extractHttpContext(context, exception, host); } if (this.config.captureStackTrace && exception.stack) { context.stackTrace = exception.stack; context.stackFrames = this.parseStackTrace(exception.stack); } if (this.config.captureEnvironment) { context.environment = this.extractEnvironmentContext(); } if (this.config.enableErrorClassification) { context.classification = this.classifyError(exception, context); } if (this.config.enablePerformanceTracking) { context.performance = this.extractPerformanceContext(); } return context; } extractHttpContext(context, exception, host) { const ctx = host.switchToHttp(); const request = ctx.getRequest(); const response = ctx.getResponse(); if (this.config.enableRequestContext) { context.request = { id: request.id, method: request.method, url: request.url, path: request.path, userAgent: request.get('user-agent'), ip: request.ip || request.connection.remoteAddress, userId: request.user?.id, sessionId: request.session?.id }; if (this.config.captureHeaders) { context.request.headers = this.sanitizeHeaders(request.headers); } if (this.config.captureBody && request.body) { context.request.body = this.sanitizeBody(request.body); } if (this.config.captureParams && request.params) { context.request.params = request.params; } if (this.config.captureQuery && request.query) { context.request.query = request.query; } } const statusCode = exception instanceof common_1.HttpException ? exception.getStatus() : common_1.HttpStatus.INTERNAL_SERVER_ERROR; context.response = { statusCode, headers: this.sanitizeHeaders(response.getHeaders()), duration: Date.now() - request.startTime }; context.statusCode = statusCode; context.traceId = request.traceId; context.requestId = request.id; context.userId = request.user?.id; context.sessionId = request.session?.id; } extractEnvironmentContext() { return { nodeVersion: process.version, platform: process.platform, hostname: process.env.HOSTNAME || 'unknown', memory: process.memoryUsage(), uptime: process.uptime() }; } extractPerformanceContext() { const memoryUsage = process.memoryUsage(); const cpuUsage = process.cpuUsage(); return { memoryUsage: memoryUsage.heapUsed, cpuUsage: cpuUsage.user + cpuUsage.system, activeConnections: process._getActiveHandles?.()?.length || 0 }; } parseStackTrace(stackTrace) { const frames = []; const lines = stackTrace.split('\n').slice(1); for (const line of lines.slice(0, this.config.maxStackTraceDepth)) { const frame = this.parseStackFrame(line); if (frame) { frames.push(frame); } } return frames; } parseStackFrame(line) { const patterns = [ /at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/, /at\s+(.+?):(\d+):(\d+)/, /at\s+(.+?)\s+\((.+?)\)/, /at\s+(.+?)$/ ]; for (const pattern of patterns) { const match = line.match(pattern); if (match) { return { function: match[1] || 'anonymous', file: match[2] || undefined, line: match[3] ? parseInt(match[3]) : undefined, column: match[4] ? parseInt(match[4]) : undefined }; } } return null; } classifyError(exception, context) { const type = this.classifyErrorType(exception); const category = this.classifyErrorCategory(exception, context); const severity = this.classifyErrorSeverity(exception, context); const fingerprint = this.generateErrorFingerprint(exception, context); const groupId = this.generateGroupId(exception, context); return { type, category, severity, fingerprint, groupId }; } classifyErrorType(exception) { if (exception instanceof common_1.HttpException) { const status = exception.getStatus(); if (status >= 400 && status < 500) { if (status === 401) return ErrorClassificationType.AUTHENTICATION; if (status === 403) return ErrorClassificationType.AUTHORIZATION; if (status === 422) return ErrorClassificationType.VALIDATION; return ErrorClassificationType.HTTP; } } const errorName = exception.constructor.name; if (errorName.includes('Database') || errorName.includes('Query')) { return ErrorClassificationType.DATABASE; } if (errorName.includes('Network') || errorName.includes('Connection')) { return ErrorClassificationType.NETWORK; } if (errorName.includes('Validation')) { return ErrorClassificationType.VALIDATION; } if (errorName.includes('Auth')) { return ErrorClassificationType.AUTHENTICATION; } return ErrorClassificationType.UNKNOWN; } classifyErrorCategory(exception, context) { if (exception instanceof common_1.HttpException) { const status = exception.getStatus(); if (status >= 400 && status < 500) { return ErrorCategory.CLIENT_ERROR; } if (status >= 500) { return ErrorCategory.SERVER_ERROR; } } const type = context.classification?.type; switch (type) { case ErrorClassificationType.DATABASE: return ErrorCategory.DATABASE_ERROR; case ErrorClassificationType.NETWORK: return ErrorCategory.NETWORK_ERROR; case ErrorClassificationType.VALIDATION: return ErrorCategory.VALIDATION_ERROR; case ErrorClassificationType.AUTHENTICATION: return ErrorCategory.AUTHENTICATION_ERROR; case ErrorClassificationType.AUTHORIZATION: return ErrorCategory.AUTHORIZATION_ERROR; default: return ErrorCategory.SYSTEM_ERROR; } } classifyErrorSeverity(exception, context) { if (exception instanceof common_1.HttpException) { const status = exception.getStatus(); if (status >= 500) return ErrorSeverity.HIGH; if (status >= 400) return ErrorSeverity.MEDIUM; return ErrorSeverity.LOW; } const errorName = exception.constructor.name; if (errorName.includes('Critical') || errorName.includes('Fatal')) { return ErrorSeverity.CRITICAL; } if (errorName.includes('Error') && !errorName.includes('Validation')) { return ErrorSeverity.HIGH; } return ErrorSeverity.MEDIUM; } generateErrorFingerprint(exception, context) { const components = [ exception.constructor.name, exception.message?.substring(0, 100), context.stackFrames?.[0]?.file, context.stackFrames?.[0]?.line?.toString() ].filter(Boolean); return this.hash(components.join(':')); } generateGroupId(exception, context) { if (!this.config.groupSimilarErrors) { return context.id; } const components = [ exception.constructor.name, this.normalizeErrorMessage(exception.message), context.stackFrames?.[0]?.file, context.stackFrames?.[0]?.function ].filter(Boolean); return this.hash(components.join(':')); } normalizeErrorMessage(message) { if (!message) return ''; return message .replace(/\d+/g, 'N') .replace(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi, 'UUID') .replace(/\b\d{4}-\d{2}-\d{2}\b/g, 'DATE') .replace(/\b\d{2}:\d{2}:\d{2}\b/g, 'TIME') .substring(0, 100); } shouldExcludeError(exception, context) { const errorType = exception.constructor.name; const errorMessage = exception.message || ''; if (this.config.excludeErrorTypes.includes(errorType)) { return true; } if (this.config.excludeErrorMessages.some(msg => errorMessage.includes(msg))) { return true; } return false; } sanitizeHeaders(headers) { const sanitized = { ...headers }; const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key', 'x-auth-token']; for (const header of sensitiveHeaders) { if (sanitized[header]) { sanitized[header] = '[REDACTED]'; } } return sanitized; } sanitizeBody(body) { if (!body || typeof body !== 'object') { return body; } const sanitized = { ...body }; const sensitiveFields = ['password', 'token', 'secret', 'key', 'auth']; for (const field of sensitiveFields) { if (sanitized[field]) { sanitized[field] = '[REDACTED]'; } } const jsonString = JSON.stringify(sanitized); if (jsonString.length > this.config.maxContextSize) { return { _truncated: true, _size: jsonString.length }; } return sanitized; } sendHttpResponse(exception, host, context) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const statusCode = exception instanceof common_1.HttpException ? exception.getStatus() : common_1.HttpStatus.INTERNAL_SERVER_ERROR; const errorResponse = { statusCode, timestamp: new Date().toISOString(), path: ctx.getRequest().url, message: exception.message || 'Internal server error', error: exception instanceof common_1.HttpException ? exception.getResponse() : 'Internal Server Error', traceId: context.traceId, requestId: context.requestId }; response.status(statusCode).json(errorResponse); } generateExceptionId() { return `exception_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } hash(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } return Math.abs(hash).toString(16); } }; exports.ExceptionWatcherFilter = ExceptionWatcherFilter; exports.ExceptionWatcherFilter = ExceptionWatcherFilter = ExceptionWatcherFilter_1 = __decorate([ (0, common_1.Catch)(), __param(1, (0, common_1.Inject)('EXCEPTION_WATCHER_CONFIG')), __metadata("design:paramtypes", [exception_watcher_service_1.ExceptionWatcherService, Object]) ], ExceptionWatcherFilter); //# sourceMappingURL=exception-watcher.filter.js.map