Initial commit
This commit is contained in:
277
services/monitoringService.ts
Normal file
277
services/monitoringService.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* Service de monitoring avancé côté client
|
||||
*/
|
||||
|
||||
export interface PerformanceMetric {
|
||||
name: string;
|
||||
value: number;
|
||||
timestamp: number;
|
||||
tags?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ErrorMetric {
|
||||
error: string;
|
||||
stack?: string;
|
||||
url: string;
|
||||
userAgent: string;
|
||||
timestamp: number;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
export interface UserAction {
|
||||
action: string;
|
||||
component: string;
|
||||
timestamp: number;
|
||||
duration?: number;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export class MonitoringService {
|
||||
private static metrics: PerformanceMetric[] = [];
|
||||
private static errors: ErrorMetric[] = [];
|
||||
private static userActions: UserAction[] = [];
|
||||
private static isEnabled = true;
|
||||
|
||||
/**
|
||||
* Initialise le service de monitoring
|
||||
*/
|
||||
static init(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
// Monitoring des erreurs JavaScript
|
||||
window.addEventListener('error', (event) => {
|
||||
this.recordError({
|
||||
error: event.message,
|
||||
stack: event.error?.stack,
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
});
|
||||
|
||||
// Monitoring des promesses rejetées
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
this.recordError({
|
||||
error: `Unhandled Promise Rejection: ${event.reason}`,
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
});
|
||||
|
||||
// Monitoring des performances de navigation
|
||||
if ('performance' in window) {
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(() => {
|
||||
this.recordNavigationMetrics();
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
// Envoi périodique des métriques
|
||||
setInterval(() => {
|
||||
this.sendMetrics();
|
||||
}, 60000); // Toutes les minutes
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une métrique de performance
|
||||
*/
|
||||
static recordMetric(name: string, value: number, tags?: Record<string, string>): void {
|
||||
if (!this.isEnabled) return;
|
||||
|
||||
this.metrics.push({
|
||||
name,
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
tags
|
||||
});
|
||||
|
||||
// Limiter le nombre de métriques en mémoire
|
||||
if (this.metrics.length > 1000) {
|
||||
this.metrics = this.metrics.slice(-500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une erreur
|
||||
*/
|
||||
static recordError(error: ErrorMetric): void {
|
||||
if (!this.isEnabled) return;
|
||||
|
||||
this.errors.push(error);
|
||||
|
||||
// Limiter le nombre d'erreurs en mémoire
|
||||
if (this.errors.length > 100) {
|
||||
this.errors = this.errors.slice(-50);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une action utilisateur
|
||||
*/
|
||||
static recordUserAction(action: string, component: string, metadata?: Record<string, any>): void {
|
||||
if (!this.isEnabled) return;
|
||||
|
||||
this.userActions.push({
|
||||
action,
|
||||
component,
|
||||
timestamp: Date.now(),
|
||||
metadata
|
||||
});
|
||||
|
||||
// Limiter le nombre d'actions en mémoire
|
||||
if (this.userActions.length > 500) {
|
||||
this.userActions = this.userActions.slice(-250);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mesure le temps d'exécution d'une fonction
|
||||
*/
|
||||
static async measureTime<T>(
|
||||
name: string,
|
||||
fn: () => Promise<T>,
|
||||
tags?: Record<string, string>
|
||||
): Promise<T> {
|
||||
const start = performance.now();
|
||||
try {
|
||||
const result = await fn();
|
||||
const duration = performance.now() - start;
|
||||
this.recordMetric(`${name}.duration`, duration, tags);
|
||||
this.recordMetric(`${name}.success`, 1, tags);
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = performance.now() - start;
|
||||
this.recordMetric(`${name}.duration`, duration, tags);
|
||||
this.recordMetric(`${name}.error`, 1, tags);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre les métriques de navigation
|
||||
*/
|
||||
private static recordNavigationMetrics(): void {
|
||||
if (!('performance' in window) || !window.performance.timing) return;
|
||||
|
||||
const timing = window.performance.timing;
|
||||
const navigation = window.performance.navigation;
|
||||
|
||||
// Temps de chargement de la page
|
||||
const pageLoadTime = timing.loadEventEnd - timing.navigationStart;
|
||||
this.recordMetric('page.load_time', pageLoadTime, {
|
||||
url: window.location.pathname
|
||||
});
|
||||
|
||||
// Temps de réponse du serveur
|
||||
const serverResponseTime = timing.responseEnd - timing.requestStart;
|
||||
this.recordMetric('page.server_response_time', serverResponseTime);
|
||||
|
||||
// Temps de rendu DOM
|
||||
const domRenderTime = timing.domContentLoadedEventEnd - timing.domLoading;
|
||||
this.recordMetric('page.dom_render_time', domRenderTime);
|
||||
|
||||
// Type de navigation
|
||||
const navigationType = navigation.type === 0 ? 'navigate' :
|
||||
navigation.type === 1 ? 'reload' :
|
||||
navigation.type === 2 ? 'back_forward' : 'unknown';
|
||||
this.recordMetric('page.navigation_type', 1, {
|
||||
type: navigationType
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie les métriques au serveur
|
||||
*/
|
||||
private static async sendMetrics(): void {
|
||||
if (this.metrics.length === 0 && this.errors.length === 0 && this.userActions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
metrics: [...this.metrics],
|
||||
errors: [...this.errors],
|
||||
userActions: [...this.userActions],
|
||||
timestamp: Date.now(),
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent
|
||||
};
|
||||
|
||||
// Envoyer au serveur (endpoint à implémenter)
|
||||
await fetch('/api/monitoring', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
// Vider les buffers après envoi réussi
|
||||
this.metrics = [];
|
||||
this.errors = [];
|
||||
this.userActions = [];
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Échec de l\'envoi des métriques:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les statistiques actuelles
|
||||
*/
|
||||
static getStats(): {
|
||||
metricsCount: number;
|
||||
errorsCount: number;
|
||||
userActionsCount: number;
|
||||
isEnabled: boolean;
|
||||
} {
|
||||
return {
|
||||
metricsCount: this.metrics.length,
|
||||
errorsCount: this.errors.length,
|
||||
userActionsCount: this.userActions.length,
|
||||
isEnabled: this.isEnabled
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Active ou désactive le monitoring
|
||||
*/
|
||||
static setEnabled(enabled: boolean): void {
|
||||
this.isEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide tous les buffers
|
||||
*/
|
||||
static clear(): void {
|
||||
this.metrics = [];
|
||||
this.errors = [];
|
||||
this.userActions = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisation automatique
|
||||
if (typeof window !== 'undefined') {
|
||||
MonitoringService.init();
|
||||
}
|
||||
|
||||
// Hook React pour le monitoring des composants
|
||||
export function useMonitoring(componentName: string) {
|
||||
const recordAction = (action: string, metadata?: Record<string, any>) => {
|
||||
MonitoringService.recordUserAction(action, componentName, metadata);
|
||||
};
|
||||
|
||||
const measureAsync = async <T>(
|
||||
name: string,
|
||||
fn: () => Promise<T>
|
||||
): Promise<T> => {
|
||||
return MonitoringService.measureTime(`${componentName}.${name}`, fn);
|
||||
};
|
||||
|
||||
return {
|
||||
recordAction,
|
||||
measureAsync
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user