UNPKG

@ahmedhegazee/nestjs-telescope

Version:

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

236 lines 10.2 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 RequestSessionTracker_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.RequestSessionTracker = void 0; const common_1 = require("@nestjs/common"); let RequestSessionTracker = RequestSessionTracker_1 = class RequestSessionTracker { constructor() { this.logger = new common_1.Logger(RequestSessionTracker_1.name); this.sessions = new Map(); this.userSessions = new Map(); this.sessionTimeout = 30 * 60 * 1000; this.cleanupInterval = 5 * 60 * 1000; this.startCleanupTimer(); } trackRequest(requestContext, responseContext, error) { try { if (requestContext.sessionId) { this.trackSessionRequest(requestContext, responseContext, error); } if (requestContext.userId) { this.trackUserSession(requestContext, responseContext, error); } } catch (trackingError) { this.logger.error('Failed to track session request:', trackingError); } } trackSessionRequest(requestContext, responseContext, error) { const sessionId = requestContext.sessionId; let session = this.sessions.get(sessionId); if (!session) { session = { id: sessionId, userId: requestContext.userId, startTime: new Date(requestContext.startTime), lastActivity: new Date(requestContext.startTime), requestCount: 0, totalDuration: 0, averageResponseTime: 0, errorCount: 0, pages: [], userAgent: requestContext.userAgent, ipAddress: requestContext.ip, isActive: true }; this.sessions.set(sessionId, session); } session.lastActivity = new Date(responseContext.endTime); session.requestCount++; session.totalDuration += responseContext.duration; session.averageResponseTime = session.totalDuration / session.requestCount; if (error || responseContext.statusCode >= 400) { session.errorCount++; } const page = this.extractPageFromUrl(requestContext.url); if (page && !session.pages.includes(page)) { session.pages.push(page); } if (requestContext.userId && !session.userId) { session.userId = requestContext.userId; } } trackUserSession(requestContext, responseContext, error) { const userId = requestContext.userId; const sessionKey = `${userId}_${requestContext.sessionId || requestContext.ip}`; let userSession = this.userSessions.get(sessionKey); if (!userSession) { userSession = { sessionId: requestContext.sessionId || this.generateSessionId(), userId, requests: [], startTime: new Date(requestContext.startTime), duration: 0, isActive: true, metadata: { userAgent: requestContext.userAgent, ipAddress: requestContext.ip, initialReferrer: requestContext.headers.referer, totalPages: 0, uniquePages: 0, bounceRate: 0, avgTimePerPage: 0 } }; this.userSessions.set(sessionKey, userSession); } const sessionRequest = { id: requestContext.id, method: requestContext.method, url: requestContext.url, statusCode: responseContext.statusCode, duration: responseContext.duration, timestamp: new Date(requestContext.startTime), error: error?.message }; userSession.requests.push(sessionRequest); userSession.duration = responseContext.endTime - userSession.startTime.getTime(); userSession.isActive = true; this.updateSessionMetadata(userSession); } updateSessionMetadata(session) { const requests = session.requests; const pages = new Set(requests.map(req => this.extractPageFromUrl(req.url)).filter(Boolean)); session.metadata.totalPages = requests.length; session.metadata.uniquePages = pages.size; session.metadata.bounceRate = pages.size === 1 ? 100 : 0; if (requests.length > 0) { const totalDuration = requests.reduce((sum, req) => sum + req.duration, 0); session.metadata.avgTimePerPage = totalDuration / requests.length; } } extractPageFromUrl(url) { try { const urlObj = new URL(url, 'http://localhost'); return urlObj.pathname; } catch { return url.split('?')[0]; } } generateSessionId() { return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } startCleanupTimer() { setInterval(() => { this.cleanupInactiveSessions(); }, this.cleanupInterval); } cleanupInactiveSessions() { const now = Date.now(); let cleanedSessions = 0; let cleanedUserSessions = 0; for (const [sessionId, session] of this.sessions) { if (now - session.lastActivity.getTime() > this.sessionTimeout) { session.isActive = false; this.sessions.delete(sessionId); cleanedSessions++; } } for (const [sessionKey, userSession] of this.userSessions) { const lastActivity = userSession.requests.length > 0 ? userSession.requests[userSession.requests.length - 1].timestamp.getTime() : userSession.startTime.getTime(); if (now - lastActivity > this.sessionTimeout) { userSession.isActive = false; userSession.endTime = new Date(lastActivity); this.userSessions.delete(sessionKey); cleanedUserSessions++; } } if (cleanedSessions > 0 || cleanedUserSessions > 0) { this.logger.debug(`Cleaned up ${cleanedSessions} sessions and ${cleanedUserSessions} user sessions`); } } getSessionInfo(sessionId) { return this.sessions.get(sessionId); } getUserSession(userId, sessionId) { if (sessionId) { return this.userSessions.get(`${userId}_${sessionId}`); } const userSessions = Array.from(this.userSessions.values()) .filter(session => session.userId === userId) .sort((a, b) => b.startTime.getTime() - a.startTime.getTime()); return userSessions[0]; } getActiveSessions() { return Array.from(this.sessions.values()) .filter(session => session.isActive) .sort((a, b) => b.lastActivity.getTime() - a.lastActivity.getTime()); } getActiveUserSessions() { return Array.from(this.userSessions.values()) .filter(session => session.isActive) .sort((a, b) => b.startTime.getTime() - a.startTime.getTime()); } getSessionsByUser(userId) { return Array.from(this.userSessions.values()) .filter(session => session.userId === userId) .sort((a, b) => b.startTime.getTime() - a.startTime.getTime()); } getSessionStats() { const allSessions = Array.from(this.sessions.values()); const activeSessions = allSessions.filter(s => s.isActive); const totalDuration = allSessions.reduce((sum, s) => { return sum + (s.lastActivity.getTime() - s.startTime.getTime()); }, 0); const totalRequests = allSessions.reduce((sum, s) => sum + s.requestCount, 0); const singlePageSessions = allSessions.filter(s => s.pages.length <= 1).length; const bounceRate = allSessions.length > 0 ? (singlePageSessions / allSessions.length) * 100 : 0; const pageVisits = new Map(); allSessions.forEach(session => { session.pages.forEach(page => { pageVisits.set(page, (pageVisits.get(page) || 0) + 1); }); }); const topPages = Array.from(pageVisits.entries()) .map(([page, visits]) => ({ page, visits })) .sort((a, b) => b.visits - a.visits) .slice(0, 10); return { totalSessions: allSessions.length, activeSessions: activeSessions.length, averageSessionDuration: allSessions.length > 0 ? totalDuration / allSessions.length : 0, averageRequestsPerSession: allSessions.length > 0 ? totalRequests / allSessions.length : 0, bounceRate, topPages }; } invalidateSession(sessionId) { const session = this.sessions.get(sessionId); if (session) { session.isActive = false; this.sessions.delete(sessionId); } } clearAllSessions() { this.sessions.clear(); this.userSessions.clear(); } }; exports.RequestSessionTracker = RequestSessionTracker; exports.RequestSessionTracker = RequestSessionTracker = RequestSessionTracker_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", []) ], RequestSessionTracker); //# sourceMappingURL=request-session-tracker.service.js.map