@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
811 lines • 32.8 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 AnalyticsService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnalyticsService = void 0;
const common_1 = require("@nestjs/common");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const telescope_service_1 = require("./telescope.service");
const performance_correlation_service_1 = require("./performance-correlation.service");
let AnalyticsService = AnalyticsService_1 = class AnalyticsService {
constructor(telescopeService, performanceCorrelationService) {
this.telescopeService = telescopeService;
this.performanceCorrelationService = performanceCorrelationService;
this.logger = new common_1.Logger(AnalyticsService_1.name);
this.destroy$ = new rxjs_1.Subject();
this.analyticsSubject = new rxjs_1.Subject();
this.currentAnalytics = this.initializeAnalytics();
}
async onModuleInit() {
this.startAnalyticsProcessing();
this.logger.log('Analytics service initialized');
}
initializeAnalytics() {
const now = new Date();
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
return {
timestamp: now,
timeRange: {
start: oneHourAgo,
end: now,
},
overview: {
totalRequests: 0,
totalErrors: 0,
totalQueries: 0,
totalCacheOps: 0,
totalJobs: 0,
averageResponseTime: 0,
errorRate: 0,
throughput: 0,
activeUsers: 0,
peakConcurrency: 0,
},
performance: {
responseTimeDistribution: {
buckets: [],
percentiles: { p50: 0, p75: 0, p90: 0, p95: 0, p99: 0 },
},
slowestEndpoints: [],
resourceUsage: {
cpu: [],
memory: [],
connections: [],
diskIO: [],
},
bottleneckAnalysis: [],
},
errors: {
errorDistribution: {
byType: [],
bySeverity: [],
byComponent: [],
},
topErrors: [],
errorTrends: [],
impactAnalysis: [],
},
database: {
queryDistribution: {
byType: [],
byTable: [],
byComplexity: [],
},
slowQueries: [],
connectionHealth: {
totalConnections: 0,
activeConnections: 0,
idleConnections: 0,
maxConnections: 0,
healthScore: 100,
issues: [],
},
indexUsage: [],
},
cache: {
hitRateDistribution: {
hitRate: 0,
missRate: 0,
byOperation: [],
byKeyPattern: [],
},
topKeys: [],
performanceMetrics: {
averageResponseTime: 0,
throughput: 0,
memoryUsage: 0,
evictionRate: 0,
trends: [],
},
evictionAnalysis: [],
},
jobs: {
queueHealth: [],
processingTimes: {
byType: [],
byQueue: [],
byStatus: [],
},
failureAnalysis: [],
throughputMetrics: [],
},
users: {
activeUsers: [],
sessionAnalysis: {
totalSessions: 0,
averageSessionDuration: 0,
averageRequestsPerSession: 0,
bounceRate: 0,
mostActiveHours: [],
},
geographicDistribution: [],
deviceAnalysis: [],
},
trends: {
trafficTrends: [],
performanceTrends: [],
errorTrends: [],
predictions: [],
},
alerts: {
activeAlerts: [],
alertTrends: [],
anomalies: [],
},
};
}
startAnalyticsProcessing() {
(0, rxjs_1.interval)(5 * 60 * 1000)
.pipe((0, operators_1.takeUntil)(this.destroy$))
.subscribe(() => {
this.updateAnalytics();
});
this.updateAnalytics();
}
async updateAnalytics() {
try {
const entries = await this.telescopeService.getEntries();
const performanceMetrics = this.performanceCorrelationService.getMetrics();
const correlations = this.performanceCorrelationService.getRecentCorrelations(1000);
this.updateOverview(entries, performanceMetrics);
this.updatePerformanceAnalytics(entries, correlations);
this.updateErrorAnalytics(entries);
this.updateDatabaseAnalytics(entries);
this.updateCacheAnalytics(entries);
this.updateJobAnalytics(entries);
this.updateUserAnalytics(entries);
this.updateTrends(entries);
this.updateAlertsAndAnomalies();
this.currentAnalytics.timestamp = new Date();
this.analyticsSubject.next({ ...this.currentAnalytics });
this.logger.debug('Analytics updated successfully');
}
catch (error) {
this.logger.error('Failed to update analytics:', error);
}
}
updateOverview(entries, performanceMetrics) {
const requests = entries.filter((e) => e.type === 'request');
const errors = entries.filter((e) => e.type === 'exception');
const queries = entries.filter((e) => e.type === 'query');
const cacheOps = entries.filter((e) => e.type === 'cache');
const jobs = entries.filter((e) => e.type === 'job');
this.currentAnalytics.overview = {
totalRequests: requests.length,
totalErrors: errors.length,
totalQueries: queries.length,
totalCacheOps: cacheOps.length,
totalJobs: jobs.length,
averageResponseTime: performanceMetrics.averageResponseTime || 0,
errorRate: performanceMetrics.errorRate || 0,
throughput: performanceMetrics.throughput || 0,
activeUsers: this.calculateActiveUsers(requests),
peakConcurrency: this.calculatePeakConcurrency(requests),
};
}
updatePerformanceAnalytics(entries, correlations) {
const requests = entries.filter((e) => e.type === 'request');
const responseTimes = requests.map((r) => r.content?.duration || 0).filter((d) => d > 0);
this.currentAnalytics.performance.responseTimeDistribution =
this.calculateResponseTimeDistribution(responseTimes);
this.currentAnalytics.performance.slowestEndpoints = this.calculateSlowestEndpoints(requests);
this.currentAnalytics.performance.resourceUsage = this.calculateResourceUsage(entries);
this.currentAnalytics.performance.bottleneckAnalysis =
this.calculateBottleneckAnalysis(correlations);
}
updateErrorAnalytics(entries) {
const errors = entries.filter((e) => e.type === 'exception');
this.currentAnalytics.errors.errorDistribution = this.calculateErrorDistribution(errors);
this.currentAnalytics.errors.topErrors = this.calculateTopErrors(errors);
this.currentAnalytics.errors.errorTrends = this.convertErrorsToTimeSeries(errors);
this.currentAnalytics.errors.impactAnalysis = this.calculateErrorImpact(errors);
}
updateDatabaseAnalytics(entries) {
const queries = entries.filter((e) => e.type === 'query');
this.currentAnalytics.database.queryDistribution = this.calculateQueryDistribution(queries);
this.currentAnalytics.database.slowQueries = this.calculateSlowQueries(queries);
this.currentAnalytics.database.connectionHealth = this.calculateConnectionHealth(queries);
this.currentAnalytics.database.indexUsage = this.calculateIndexUsage(queries);
}
updateCacheAnalytics(entries) {
const cacheOps = entries.filter((e) => e.type === 'cache');
this.currentAnalytics.cache.hitRateDistribution = this.calculateCacheDistribution(cacheOps);
this.currentAnalytics.cache.topKeys = this.calculateTopCacheKeys(cacheOps);
this.currentAnalytics.cache.performanceMetrics = this.calculateCachePerformance(cacheOps);
this.currentAnalytics.cache.evictionAnalysis = this.calculateEvictionAnalysis(cacheOps);
}
updateJobAnalytics(entries) {
const jobs = entries.filter((e) => e.type === 'job');
this.currentAnalytics.jobs.queueHealth = this.calculateQueueHealth(jobs);
this.currentAnalytics.jobs.processingTimes = this.calculateJobDistribution(jobs);
this.currentAnalytics.jobs.failureAnalysis = this.calculateJobFailureAnalysis(jobs);
this.currentAnalytics.jobs.throughputMetrics = this.calculateJobThroughput(jobs);
}
updateUserAnalytics(entries) {
const requests = entries.filter((e) => e.type === 'request');
this.currentAnalytics.users.activeUsers = this.calculateActiveUserMetrics(requests);
this.currentAnalytics.users.sessionAnalysis = this.calculateSessionAnalysis(requests);
this.currentAnalytics.users.geographicDistribution =
this.calculateGeographicDistribution(requests);
this.currentAnalytics.users.deviceAnalysis = this.calculateDeviceAnalysis(requests);
}
updateTrends(entries) {
this.currentAnalytics.trends.trafficTrends = this.calculateTrafficTrends(entries);
this.currentAnalytics.trends.performanceTrends = this.calculatePerformanceTrends(entries);
this.currentAnalytics.trends.errorTrends = this.convertErrorsToTimeSeries(entries.filter((e) => e.type === 'exception'));
this.currentAnalytics.trends.predictions = this.calculatePredictions(entries);
}
updateAlertsAndAnomalies() {
this.currentAnalytics.alerts = {
activeAlerts: [],
alertTrends: [],
anomalies: [],
};
}
calculateActiveUsers(requests) {
const uniqueUsers = new Set(requests.map((r) => r.content?.userId || r.content?.ip).filter(Boolean));
return uniqueUsers.size;
}
calculatePeakConcurrency(requests) {
return Math.max(1, Math.floor(requests.length / 60));
}
calculateResponseTimeDistribution(responseTimes) {
if (responseTimes.length === 0) {
return {
buckets: [],
percentiles: { p50: 0, p75: 0, p90: 0, p95: 0, p99: 0 },
};
}
const sorted = [...responseTimes].sort((a, b) => a - b);
const buckets = [
{ range: '<100ms', count: 0, percentage: 0 },
{ range: '100-500ms', count: 0, percentage: 0 },
{ range: '500ms-1s', count: 0, percentage: 0 },
{ range: '1-5s', count: 0, percentage: 0 },
{ range: '>5s', count: 0, percentage: 0 },
];
sorted.forEach((time) => {
if (time !== undefined) {
if (time < 100)
buckets[0].count++;
else if (time < 500)
buckets[1].count++;
else if (time < 1000)
buckets[2].count++;
else if (time < 5000)
buckets[3].count++;
else
buckets[4].count++;
}
});
buckets.forEach((bucket) => {
bucket.percentage = (bucket.count / sorted.length) * 100;
});
return {
buckets,
percentiles: {
p50: sorted[Math.floor(sorted.length * 0.5)] || 0,
p75: sorted[Math.floor(sorted.length * 0.75)] || 0,
p90: sorted[Math.floor(sorted.length * 0.9)] || 0,
p95: sorted[Math.floor(sorted.length * 0.95)] || 0,
p99: sorted[Math.floor(sorted.length * 0.99)] || 0,
},
};
}
calculateSlowestEndpoints(requests) {
const endpointMap = new Map();
requests.forEach((request) => {
const endpoint = `${request.content?.method || 'GET'} ${request.content?.path || '/'}`;
const duration = request.content?.duration || 0;
const hasError = request.content?.statusCode >= 400;
if (!endpointMap.has(endpoint)) {
endpointMap.set(endpoint, {
count: 0,
totalTime: 0,
times: [],
errors: 0,
});
}
const data = endpointMap.get(endpoint);
data.count++;
data.totalTime += duration;
data.times.push(duration);
if (hasError)
data.errors++;
});
return Array.from(endpointMap.entries())
.map(([endpoint, data]) => {
const parts = endpoint.split(' ', 2);
const method = parts[0] || 'GET';
const path = parts[1] || '/';
const sortedTimes = data.times.sort((a, b) => a - b);
const p95Index = Math.floor(sortedTimes.length * 0.95);
return {
endpoint: path,
method,
count: data.count,
averageTime: data.totalTime / data.count,
p95Time: sortedTimes[p95Index] || 0,
errorRate: (data.errors / data.count) * 100,
throughput: data.count / 3600,
};
})
.sort((a, b) => b.averageTime - a.averageTime)
.slice(0, 10);
}
calculateResourceUsage(entries) {
return {
cpu: [],
memory: [],
connections: [],
diskIO: [],
};
}
calculateBottleneckAnalysis(correlations) {
const bottleneckMap = new Map();
correlations.forEach((correlation) => {
correlation.bottlenecks?.forEach((bottleneck) => {
const key = bottleneck.component;
if (!bottleneckMap.has(key)) {
bottleneckMap.set(key, {
count: 0,
totalImpact: 0,
severities: [],
});
}
const data = bottleneckMap.get(key);
data.count++;
data.totalImpact += bottleneck.percentage || 0;
data.severities.push(bottleneck.severity);
});
});
return Array.from(bottleneckMap.entries())
.map(([component, data]) => {
const mostCommonSeverity = this.getMostCommon(data.severities);
return {
component,
severity: mostCommonSeverity,
frequency: data.count,
averageImpact: data.totalImpact / data.count,
description: `${component} bottleneck occurred ${data.count} times`,
recommendation: `Optimize ${component} performance`,
};
})
.sort((a, b) => b.frequency - a.frequency)
.slice(0, 10);
}
getMostCommon(items) {
const counts = new Map();
items.forEach((item) => {
counts.set(item, (counts.get(item) || 0) + 1);
});
let maxCount = 0;
let mostCommon = '';
counts.forEach((count, item) => {
if (count > maxCount) {
maxCount = count;
mostCommon = item;
}
});
return mostCommon;
}
calculateErrorDistribution(errors) {
const typeMap = new Map();
const severityMap = new Map();
const componentMap = new Map();
errors.forEach((error) => {
const errorType = error.content?.error?.name || 'Unknown';
const severity = error.content?.severity || 'medium';
const component = error.content?.component || 'application';
typeMap.set(errorType, (typeMap.get(errorType) || 0) + 1);
severityMap.set(severity, (severityMap.get(severity) || 0) + 1);
componentMap.set(component, (componentMap.get(component) || 0) + 1);
});
const total = errors.length || 1;
return {
byType: Array.from(typeMap.entries()).map(([type, count]) => ({
type,
count,
percentage: (count / total) * 100,
})),
bySeverity: Array.from(severityMap.entries()).map(([severity, count]) => ({
severity,
count,
percentage: (count / total) * 100,
})),
byComponent: Array.from(componentMap.entries()).map(([component, count]) => ({
component,
count,
percentage: (count / total) * 100,
})),
};
}
calculateTopErrors(errors) {
const errorMap = new Map();
errors.forEach((error) => {
const errorType = error.content?.error?.name || 'Unknown';
const message = error.content?.error?.message || 'No message';
const userId = error.content?.userId || error.content?.ip;
if (!errorMap.has(errorType)) {
errorMap.set(errorType, {
count: 0,
firstSeen: error.timestamp,
lastSeen: error.timestamp,
affectedUsers: new Set(),
message,
});
}
const data = errorMap.get(errorType);
data.count++;
data.lastSeen = new Date(Math.max(data.lastSeen.getTime(), error.timestamp.getTime()));
data.firstSeen = new Date(Math.min(data.firstSeen.getTime(), error.timestamp.getTime()));
if (userId)
data.affectedUsers.add(userId);
});
return Array.from(errorMap.entries())
.map(([errorType, data]) => ({
errorType,
message: data.message,
count: data.count,
firstSeen: data.firstSeen,
lastSeen: data.lastSeen,
impactScore: this.calculateErrorImpactScore(data.count, data.affectedUsers.size),
affectedUsers: data.affectedUsers.size,
}))
.sort((a, b) => b.impactScore - a.impactScore)
.slice(0, 10);
}
calculateErrorImpact(errors) {
const impactMap = new Map();
errors.forEach((error) => {
const errorType = error.content?.error?.name || 'Unknown';
const responseTime = error.content?.responseTime || 0;
if (!impactMap.has(errorType)) {
impactMap.set(errorType, {
count: 0,
performanceImpact: [],
userCount: 0,
});
}
const data = impactMap.get(errorType);
data.count++;
if (responseTime > 0)
data.performanceImpact.push(responseTime);
});
return Array.from(impactMap.entries())
.map(([errorType, data]) => {
const avgPerformanceImpact = data.performanceImpact.length > 0
? data.performanceImpact.reduce((sum, val) => sum + val, 0) /
data.performanceImpact.length
: 0;
const userImpact = (data.userCount / this.currentAnalytics.overview.activeUsers) * 100;
const businessImpact = this.calculateBusinessImpact(data.count, avgPerformanceImpact);
return {
errorType,
performanceImpact: avgPerformanceImpact,
userImpact,
businessImpact,
recommendations: this.generateErrorRecommendations(errorType, avgPerformanceImpact, data.count),
};
})
.sort((a, b) => b.businessImpact - a.businessImpact);
}
calculateQueryDistribution(queries) {
return {
byType: [],
byTable: [],
byComplexity: [],
};
}
calculateSlowQueries(queries) {
return [];
}
calculateConnectionHealth(queries) {
return {
totalConnections: 0,
activeConnections: 0,
idleConnections: 0,
maxConnections: 0,
healthScore: 100,
issues: [],
};
}
calculateIndexUsage(queries) {
return [];
}
calculateCacheDistribution(cacheOps) {
if (cacheOps.length === 0) {
return {
hitRate: 0,
missRate: 0,
byOperation: [],
byKeyPattern: [],
};
}
const hits = cacheOps.filter((op) => op.content?.cache?.hit === true).length;
const total = cacheOps.length;
const hitRate = (hits / total) * 100;
const missRate = 100 - hitRate;
const operationMap = new Map();
cacheOps.forEach((op) => {
const operation = op.content?.cache?.operation || 'unknown';
const isHit = op.content?.cache?.hit === true;
if (!operationMap.has(operation)) {
operationMap.set(operation, { total: 0, hits: 0 });
}
const data = operationMap.get(operation);
data.total++;
if (isHit)
data.hits++;
});
const byOperation = Array.from(operationMap.entries()).map(([operation, data]) => ({
operation,
hitRate: (data.hits / data.total) * 100,
count: data.total,
}));
const patternMap = new Map();
cacheOps.forEach((op) => {
const pattern = op.content?.cache?.keyPattern || this.extractKeyPattern(op.content?.cache?.key || '');
const isHit = op.content?.cache?.hit === true;
if (!patternMap.has(pattern)) {
patternMap.set(pattern, { total: 0, hits: 0 });
}
const data = patternMap.get(pattern);
data.total++;
if (isHit)
data.hits++;
});
const byKeyPattern = Array.from(patternMap.entries()).map(([pattern, data]) => ({
pattern,
hitRate: (data.hits / data.total) * 100,
count: data.total,
}));
return {
hitRate,
missRate,
byOperation,
byKeyPattern,
};
}
calculateTopCacheKeys(cacheOps) {
return [];
}
calculateCachePerformance(cacheOps) {
return {
averageResponseTime: 0,
throughput: 0,
memoryUsage: 0,
evictionRate: 0,
trends: [],
};
}
calculateEvictionAnalysis(cacheOps) {
return [];
}
calculateQueueHealth(jobs) {
return [];
}
calculateJobDistribution(jobs) {
return {
byType: [],
byQueue: [],
byStatus: [],
};
}
calculateJobFailureAnalysis(jobs) {
return [];
}
calculateJobThroughput(jobs) {
return [];
}
calculateActiveUserMetrics(requests) {
const userMap = new Map();
requests.forEach((request) => {
const userId = request.content?.userId || request.content?.ip || 'anonymous';
const responseTime = request.content?.duration || 0;
const hasError = request.content?.responseStatus >= 400;
if (!userMap.has(userId)) {
userMap.set(userId, {
sessionCount: 0,
requestCount: 0,
errorCount: 0,
responseTimes: [],
lastActive: request.timestamp,
});
}
const data = userMap.get(userId);
data.requestCount++;
if (hasError)
data.errorCount++;
if (responseTime > 0)
data.responseTimes.push(responseTime);
data.lastActive = new Date(Math.max(data.lastActive.getTime(), request.timestamp.getTime()));
});
return Array.from(userMap.entries())
.map(([userId, data]) => ({
userId,
sessionCount: data.sessionCount,
requestCount: data.requestCount,
errorCount: data.errorCount,
averageResponseTime: data.responseTimes.length > 0
? data.responseTimes.reduce((sum, val) => sum + val, 0) / data.responseTimes.length
: 0,
lastActive: data.lastActive,
}))
.sort((a, b) => b.requestCount - a.requestCount)
.slice(0, 100);
}
calculateSessionAnalysis(requests) {
return {
totalSessions: 0,
averageSessionDuration: 0,
averageRequestsPerSession: 0,
bounceRate: 0,
mostActiveHours: [],
};
}
calculateGeographicDistribution(requests) {
return [];
}
calculateDeviceAnalysis(requests) {
return [];
}
calculateTrafficTrends(entries) {
const requests = entries.filter((e) => e.type === 'request');
const hourlyBuckets = new Map();
requests.forEach((request) => {
const hour = new Date(request.timestamp);
hour.setMinutes(0, 0, 0);
const hourKey = hour.toISOString();
hourlyBuckets.set(hourKey, (hourlyBuckets.get(hourKey) || 0) + 1);
});
return Array.from(hourlyBuckets.entries())
.map(([timestamp, value]) => ({
timestamp: new Date(timestamp),
value,
label: 'Requests per hour',
}))
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
}
calculatePerformanceTrends(entries) {
const requests = entries.filter((e) => e.type === 'request' && e.content?.duration);
const hourlyBuckets = new Map();
requests.forEach((request) => {
const hour = new Date(request.timestamp);
hour.setMinutes(0, 0, 0);
const hourKey = hour.toISOString();
if (!hourlyBuckets.has(hourKey)) {
hourlyBuckets.set(hourKey, []);
}
hourlyBuckets.get(hourKey).push(request.content.duration);
});
return Array.from(hourlyBuckets.entries())
.map(([timestamp, durations]) => ({
timestamp: new Date(timestamp),
value: durations.reduce((sum, val) => sum + val, 0) / durations.length,
label: 'Average response time (ms)',
}))
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
}
calculatePredictions(entries) {
const predictions = [];
const trafficTrend = this.calculateTrafficTrends(entries);
if (trafficTrend.length >= 3) {
const recent = trafficTrend.slice(-3);
const avgChange = (recent[2].value - recent[0].value) / 2;
const predicted = recent[2].value + avgChange;
predictions.push({
metric: 'traffic',
prediction: Math.max(0, predicted),
confidence: 0.7,
timeframe: 'next_hour',
factors: ['historical_trend', 'time_of_day'],
});
}
const perfTrend = this.calculatePerformanceTrends(entries);
if (perfTrend.length >= 3) {
const recent = perfTrend.slice(-3);
const avgChange = (recent[2].value - recent[0].value) / 2;
const predicted = recent[2].value + avgChange;
predictions.push({
metric: 'response_time',
prediction: Math.max(0, predicted),
confidence: 0.6,
timeframe: 'next_hour',
factors: ['performance_trend', 'system_load'],
});
}
return predictions;
}
getAnalytics() {
return { ...this.currentAnalytics };
}
getAnalyticsStream() {
return this.analyticsSubject.asObservable().pipe((0, operators_1.shareReplay)(1));
}
getAnalyticsForTimeRange(start, end) {
return Promise.resolve({ ...this.currentAnalytics });
}
async refreshAnalytics() {
await this.updateAnalytics();
}
onDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
calculateErrorImpactScore(count, affectedUsers) {
const frequencyScore = Math.min(count / 100, 1) * 50;
const userScore = Math.min(affectedUsers / 10, 1) * 50;
return frequencyScore + userScore;
}
calculateBusinessImpact(errorCount, avgResponseTime) {
const errorImpact = Math.min(errorCount / 100, 1) * 60;
const performanceImpact = Math.min(avgResponseTime / 5000, 1) * 40;
return errorImpact + performanceImpact;
}
generateErrorRecommendations(errorType, performanceImpact, count) {
const recommendations = [];
if (count > 10) {
recommendations.push('Investigate error frequency and implement preventive measures');
}
if (performanceImpact > 1000) {
recommendations.push('Optimize error handling to reduce performance impact');
}
if (errorType.toLowerCase().includes('database')) {
recommendations.push('Review database queries and connection handling');
}
if (errorType.toLowerCase().includes('timeout')) {
recommendations.push('Increase timeout values or optimize slow operations');
}
return recommendations.length > 0
? recommendations
: ['Monitor error patterns and implement appropriate fixes'];
}
extractKeyPattern(key) {
if (!key)
return 'unknown';
return key
.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(/[a-f0-9]{32}/gi, 'HASH32')
.replace(/[a-f0-9]{40}/gi, 'HASH40')
.replace(/[a-f0-9]{64}/gi, 'HASH64')
.replace(/\d{4}-\d{2}-\d{2}/g, 'DATE')
.replace(/\d{2}:\d{2}:\d{2}/g, 'TIME');
}
convertErrorsToTimeSeries(errors) {
const now = new Date();
const hourlyBuckets = new Map();
for (let i = 23; i >= 0; i--) {
const hourAgo = new Date(now.getTime() - i * 60 * 60 * 1000);
const hourKey = hourAgo.toISOString().slice(0, 13);
hourlyBuckets.set(hourKey, 0);
}
errors.forEach((error) => {
const timestamp = new Date(error.timestamp);
const hourKey = timestamp.toISOString().slice(0, 13);
const currentCount = hourlyBuckets.get(hourKey) || 0;
hourlyBuckets.set(hourKey, currentCount + 1);
});
return Array.from(hourlyBuckets.entries()).map(([hourKey, count]) => ({
timestamp: new Date(hourKey + ':00:00.000Z'),
value: count,
}));
}
};
exports.AnalyticsService = AnalyticsService;
exports.AnalyticsService = AnalyticsService = AnalyticsService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [telescope_service_1.TelescopeService,
performance_correlation_service_1.PerformanceCorrelationService])
], AnalyticsService);
//# sourceMappingURL=analytics.service.js.map