@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
JavaScript
;
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