- Add in-app notification system with database storage - Implement bell icon component with unread count badge in dashboard header - Add full Notifications page tab with pagination and filtering (All/Unread/Read) - Implement real-time WebSocket push notifications to all connected clients - Add notification API endpoints: GET, PUT /read, DELETE, POST /clear - Fix critical bug in notificationService where 'status' variable was undefined - Add Notifications route to App.js routing - Enhanced UI with notification popup in header and dedicated full-page view - Auto-reconnect WebSocket with 5s timeout on disconnect - Support rich notification content with resource details and timestamps
129 lines
3.4 KiB
JavaScript
129 lines
3.4 KiB
JavaScript
const db = require('./database');
|
|
|
|
class MetricsCollector {
|
|
constructor() {
|
|
this.apiMetrics = new Map();
|
|
this.errorCounts = new Map();
|
|
this.startTime = Date.now();
|
|
}
|
|
|
|
// Track API request metrics
|
|
trackApiRequest(endpoint, method, statusCode, duration) {
|
|
const key = `${method}:${endpoint}`;
|
|
if (!this.apiMetrics.has(key)) {
|
|
this.apiMetrics.set(key, {
|
|
endpoint,
|
|
method,
|
|
totalRequests: 0,
|
|
successCount: 0,
|
|
errorCount: 0,
|
|
totalDuration: 0,
|
|
minDuration: Infinity,
|
|
maxDuration: 0,
|
|
statusCodes: {}
|
|
});
|
|
}
|
|
|
|
const metrics = this.apiMetrics.get(key);
|
|
metrics.totalRequests++;
|
|
metrics.totalDuration += duration;
|
|
metrics.minDuration = Math.min(metrics.minDuration, duration);
|
|
metrics.maxDuration = Math.max(metrics.maxDuration, duration);
|
|
|
|
if (statusCode >= 200 && statusCode < 400) {
|
|
metrics.successCount++;
|
|
} else {
|
|
metrics.errorCount++;
|
|
}
|
|
|
|
metrics.statusCodes[statusCode] = (metrics.statusCodes[statusCode] || 0) + 1;
|
|
}
|
|
|
|
// Track application errors
|
|
trackError(error, context = {}) {
|
|
const errorKey = error.message || 'Unknown Error';
|
|
const errorData = {
|
|
message: error.message,
|
|
stack: error.stack,
|
|
context,
|
|
timestamp: new Date().toISOString(),
|
|
count: (this.errorCounts.get(errorKey) || 0) + 1
|
|
};
|
|
|
|
this.errorCounts.set(errorKey, errorData.count);
|
|
|
|
// Log to database
|
|
try {
|
|
db.prepare(`
|
|
INSERT INTO error_logs (error_message, stack_trace, context, occurred_at)
|
|
VALUES (?, ?, ?, datetime('now'))
|
|
`).run(
|
|
error.message,
|
|
error.stack || '',
|
|
JSON.stringify(context)
|
|
);
|
|
} catch (err) {
|
|
// Silent failure - don't crash on logging errors
|
|
}
|
|
|
|
return errorData;
|
|
}
|
|
|
|
// Get current metrics snapshot
|
|
getMetrics() {
|
|
const apiStats = [];
|
|
this.apiMetrics.forEach((metrics, key) => {
|
|
const avgDuration = metrics.totalRequests > 0
|
|
? Math.round(metrics.totalDuration / metrics.totalRequests)
|
|
: 0;
|
|
|
|
apiStats.push({
|
|
endpoint: metrics.endpoint,
|
|
method: metrics.method,
|
|
totalRequests: metrics.totalRequests,
|
|
successRate: metrics.totalRequests > 0
|
|
? ((metrics.successCount / metrics.totalRequests) * 100).toFixed(2)
|
|
: 0,
|
|
avgResponseTime: avgDuration,
|
|
minResponseTime: metrics.minDuration === Infinity ? 0 : metrics.minDuration,
|
|
maxResponseTime: metrics.maxDuration,
|
|
statusCodes: metrics.statusCodes
|
|
});
|
|
});
|
|
|
|
return {
|
|
uptime: Math.floor((Date.now() - this.startTime) / 1000),
|
|
apiStats: apiStats.sort((a, b) => b.totalRequests - a.totalRequests),
|
|
errorCount: Array.from(this.errorCounts.values()).reduce((sum, count) => sum + count, 0),
|
|
uniqueErrors: this.errorCounts.size
|
|
};
|
|
}
|
|
|
|
// Get recent errors from database
|
|
getRecentErrors(limit = 50) {
|
|
try {
|
|
return db.prepare(`
|
|
SELECT
|
|
id,
|
|
error_message,
|
|
stack_trace,
|
|
context,
|
|
REPLACE(occurred_at, ' ', 'T') || 'Z' as occurred_at
|
|
FROM error_logs
|
|
ORDER BY occurred_at DESC
|
|
LIMIT ?
|
|
`).all(limit);
|
|
} catch (err) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Clear in-memory metrics (for testing/reset)
|
|
reset() {
|
|
this.apiMetrics.clear();
|
|
this.errorCounts.clear();
|
|
}
|
|
}
|
|
|
|
module.exports = new MetricsCollector();
|