@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
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 __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