UNPKG

@ahmedhegazee/nestjs-telescope

Version:

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

344 lines 15.3 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 QueryWatcherInterceptor_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueryWatcherInterceptor = void 0; const common_1 = require("@nestjs/common"); const typeorm_1 = require("typeorm"); const query_watcher_service_1 = require("./query-watcher.service"); let QueryWatcherInterceptor = QueryWatcherInterceptor_1 = class QueryWatcherInterceptor { constructor(queryWatcher, dataSource) { this.queryWatcher = queryWatcher; this.dataSource = dataSource; this.logger = new common_1.Logger(QueryWatcherInterceptor_1.name); this.originalMethods = new Map(); this.queryCounter = 0; } async setupInterception() { try { await this.interceptQueryRunner(); await this.interceptQueryBuilder(); await this.interceptRepository(); this.logger.log('Query interception setup completed'); } catch (error) { this.logger.error('Failed to setup query interception:', error); } } async interceptQueryRunner() { const queryRunner = this.dataSource.createQueryRunner(); const originalQuery = queryRunner.query.bind(queryRunner); queryRunner.query = async (query, parameters) => { const context = this.createQueryContext(query, parameters); try { const result = await originalQuery(query, parameters); context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.resultCount = Array.isArray(result) ? result.length : 1; this.queryWatcher.trackQuery(context); return result; } catch (error) { context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.error = error; this.queryWatcher.trackQuery(context); throw error; } }; } async interceptQueryBuilder() { const originalMethods = { getMany: typeorm_1.SelectQueryBuilder.prototype.getMany, getOne: typeorm_1.SelectQueryBuilder.prototype.getOne, getRawMany: typeorm_1.SelectQueryBuilder.prototype.getRawMany, getRawOne: typeorm_1.SelectQueryBuilder.prototype.getRawOne, getCount: typeorm_1.SelectQueryBuilder.prototype.getCount, execute: typeorm_1.SelectQueryBuilder.prototype.execute }; typeorm_1.SelectQueryBuilder.prototype.getMany = async function () { const context = this.createQueryBuilderContext(this, 'select'); try { const result = await originalMethods.getMany.call(this); context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.resultCount = result.length; this.queryWatcher.trackQuery(context); return result; } catch (error) { context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.error = error; this.queryWatcher.trackQuery(context); throw error; } }.bind(this); typeorm_1.SelectQueryBuilder.prototype.getOne = async function () { const context = this.createQueryBuilderContext(this, 'select'); try { const result = await originalMethods.getOne.call(this); context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.resultCount = result ? 1 : 0; this.queryWatcher.trackQuery(context); return result; } catch (error) { context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.error = error; this.queryWatcher.trackQuery(context); throw error; } }.bind(this); typeorm_1.SelectQueryBuilder.prototype.getRawMany = async function () { const context = this.createQueryBuilderContext(this, 'select'); try { const result = await originalMethods.getRawMany.call(this); context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.resultCount = result.length; this.queryWatcher.trackQuery(context); return result; } catch (error) { context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.error = error; this.queryWatcher.trackQuery(context); throw error; } }.bind(this); typeorm_1.SelectQueryBuilder.prototype.getRawOne = async function () { const context = this.createQueryBuilderContext(this, 'select'); try { const result = await originalMethods.getRawOne.call(this); context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.resultCount = result ? 1 : 0; this.queryWatcher.trackQuery(context); return result; } catch (error) { context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.error = error; this.queryWatcher.trackQuery(context); throw error; } }.bind(this); typeorm_1.SelectQueryBuilder.prototype.getCount = async function () { const context = this.createQueryBuilderContext(this, 'select'); try { const result = await originalMethods.getCount.call(this); context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.resultCount = 1; this.queryWatcher.trackQuery(context); return result; } catch (error) { context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.error = error; this.queryWatcher.trackQuery(context); throw error; } }.bind(this); typeorm_1.SelectQueryBuilder.prototype.execute = async function () { const context = this.createQueryBuilderContext(this, 'raw'); try { const result = await originalMethods.execute.call(this); context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.affectedRows = result.affected || 0; this.queryWatcher.trackQuery(context); return result; } catch (error) { context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.error = error; this.queryWatcher.trackQuery(context); throw error; } }.bind(this); } async interceptRepository() { const repositories = this.dataSource.entityMetadatas.map(metadata => this.dataSource.getRepository(metadata.target)); for (const repository of repositories) { this.interceptRepositoryMethods(repository); } } interceptRepositoryMethods(repository) { const methodsToIntercept = [ 'find', 'findOne', 'findOneBy', 'findBy', 'findAndCount', 'findAndCountBy', 'count', 'countBy', 'save', 'insert', 'update', 'upsert', 'delete', 'remove' ]; for (const methodName of methodsToIntercept) { if (typeof repository[methodName] === 'function') { const originalMethod = repository[methodName]; repository[methodName] = async (...args) => { const context = this.createRepositoryContext(repository, methodName, args); try { const result = await originalMethod.apply(repository, args); context.endTime = Date.now(); context.duration = context.endTime - context.startTime; if (Array.isArray(result)) { context.resultCount = result.length; } else if (result && typeof result === 'object') { context.resultCount = 1; context.affectedRows = result.affected || result.generatedMaps?.length || 1; } this.queryWatcher.trackQuery(context); return result; } catch (error) { context.endTime = Date.now(); context.duration = context.endTime - context.startTime; context.error = error; this.queryWatcher.trackQuery(context); throw error; } }; } } } createQueryContext(sql, parameters) { return { id: this.generateQueryId(), sql: sql.trim(), parameters: parameters || [], startTime: Date.now(), operation: this.detectOperationType(sql), tableName: this.extractTableName(sql), stack: this.captureStack(), traceId: this.generateTraceId() }; } createQueryBuilderContext(queryBuilder, operation) { const sql = queryBuilder.getSql(); const parameters = queryBuilder.getParameters(); return { id: this.generateQueryId(), sql: sql.trim(), parameters: Object.values(parameters), startTime: Date.now(), operation: operation, entityName: queryBuilder.expressionMap?.mainAlias?.metadata?.name, tableName: queryBuilder.expressionMap?.mainAlias?.metadata?.tableName, stack: this.captureStack(), traceId: this.generateTraceId() }; } createRepositoryContext(repository, method, args) { const entityName = repository.metadata?.name; const tableName = repository.metadata?.tableName; return { id: this.generateQueryId(), sql: `Repository.${method}`, parameters: args, startTime: Date.now(), operation: this.mapRepositoryMethodToOperation(method), entityName, tableName, stack: this.captureStack(), traceId: this.generateTraceId() }; } detectOperationType(sql) { const normalizedSql = sql.toLowerCase().trim(); if (normalizedSql.startsWith('select')) return 'select'; if (normalizedSql.startsWith('insert')) return 'insert'; if (normalizedSql.startsWith('update')) return 'update'; if (normalizedSql.startsWith('delete')) return 'delete'; return 'raw'; } mapRepositoryMethodToOperation(method) { const selectMethods = ['find', 'findOne', 'findOneBy', 'findBy', 'findAndCount', 'findAndCountBy', 'count', 'countBy']; const insertMethods = ['save', 'insert']; const updateMethods = ['update', 'upsert']; const deleteMethods = ['delete', 'remove']; if (selectMethods.includes(method)) return 'select'; if (insertMethods.includes(method)) return 'insert'; if (updateMethods.includes(method)) return 'update'; if (deleteMethods.includes(method)) return 'delete'; return 'raw'; } extractTableName(sql) { const normalizedSql = sql.toLowerCase().trim(); let match = normalizedSql.match(/from\s+`?(\w+)`?/i); if (match) return match[1]; match = normalizedSql.match(/insert\s+into\s+`?(\w+)`?/i); if (match) return match[1]; match = normalizedSql.match(/update\s+`?(\w+)`?/i); if (match) return match[1]; match = normalizedSql.match(/delete\s+from\s+`?(\w+)`?/i); if (match) return match[1]; return undefined; } captureStack() { const stack = new Error().stack; if (!stack) return ''; const lines = stack.split('\n'); const filteredLines = lines.filter(line => !line.includes('QueryWatcherInterceptor') && !line.includes('node_modules/typeorm') && !line.includes('node_modules/@nestjs')); return filteredLines.slice(0, 10).join('\n'); } generateQueryId() { return `query_${Date.now()}_${++this.queryCounter}`; } generateTraceId() { return `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } async getConnectionInfo() { const driver = this.dataSource.driver; const pool = driver.pool; return { id: this.dataSource.name || 'default', database: this.dataSource.options.database, host: this.dataSource.options.host || 'localhost', port: this.dataSource.options.port || 5432, username: this.dataSource.options.username || 'unknown', poolSize: pool?.config?.max || 10, activeConnections: pool?.totalCount || 0, idleConnections: pool?.idleCount || 0, waitingConnections: pool?.waitingCount || 0, acquiredConnections: pool?.acquiredCount || 0 }; } async cleanup() { this.logger.log('Query interception cleanup completed'); } }; exports.QueryWatcherInterceptor = QueryWatcherInterceptor; exports.QueryWatcherInterceptor = QueryWatcherInterceptor = QueryWatcherInterceptor_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [query_watcher_service_1.QueryWatcherService, typeorm_1.DataSource]) ], QueryWatcherInterceptor); //# sourceMappingURL=query-watcher.interceptor.js.map