307 lines
7.4 KiB
TypeScript
307 lines
7.4 KiB
TypeScript
|
|
import * as Sentry from '@sentry/react';
|
||
|
|
import { BrowserTracing } from '@sentry/tracing';
|
||
|
|
|
||
|
|
// Configuration Sentry
|
||
|
|
const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN || '';
|
||
|
|
|
||
|
|
export class MonitoringService {
|
||
|
|
private static instance: MonitoringService;
|
||
|
|
private isInitialized = false;
|
||
|
|
|
||
|
|
private constructor() {}
|
||
|
|
|
||
|
|
static getInstance(): MonitoringService {
|
||
|
|
if (!MonitoringService.instance) {
|
||
|
|
MonitoringService.instance = new MonitoringService();
|
||
|
|
}
|
||
|
|
return MonitoringService.instance;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialiser Sentry
|
||
|
|
initializeSentry(): void {
|
||
|
|
if (this.isInitialized || !SENTRY_DSN) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Sentry.init({
|
||
|
|
dsn: SENTRY_DSN,
|
||
|
|
integrations: [
|
||
|
|
new BrowserTracing({
|
||
|
|
tracePropagationTargets: ['localhost', 'veza-platform.com'],
|
||
|
|
}),
|
||
|
|
],
|
||
|
|
tracesSampleRate: 1.0,
|
||
|
|
environment: process.env.NODE_ENV,
|
||
|
|
release: process.env.REACT_APP_VERSION || '1.0.0',
|
||
|
|
beforeSend(event) {
|
||
|
|
// Filtrer les erreurs sensibles
|
||
|
|
if (event.exception) {
|
||
|
|
const exception = event.exception.values?.[0];
|
||
|
|
if (exception?.value?.includes('password') ||
|
||
|
|
exception?.value?.includes('token') ||
|
||
|
|
exception?.value?.includes('key')) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return event;
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
this.isInitialized = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer une erreur
|
||
|
|
captureException(error: Error, context?: Record<string, any>): void {
|
||
|
|
if (!this.isInitialized) {
|
||
|
|
console.error('Sentry not initialized');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Sentry.captureException(error, {
|
||
|
|
extra: context,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer un message
|
||
|
|
captureMessage(message: string, level: Sentry.SeverityLevel = 'info'): void {
|
||
|
|
if (!this.isInitialized) {
|
||
|
|
console.log(message);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Sentry.captureMessage(message, {
|
||
|
|
level,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ajouter du contexte utilisateur
|
||
|
|
setUser(user: {
|
||
|
|
id: string;
|
||
|
|
email: string;
|
||
|
|
username?: string;
|
||
|
|
}): void {
|
||
|
|
if (!this.isInitialized) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Sentry.setUser({
|
||
|
|
id: user.id,
|
||
|
|
email: user.email,
|
||
|
|
username: user.username,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ajouter des tags
|
||
|
|
setTag(key: string, value: string): void {
|
||
|
|
if (!this.isInitialized) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Sentry.setTag(key, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ajouter du contexte
|
||
|
|
setContext(name: string, context: Record<string, any>): void {
|
||
|
|
if (!this.isInitialized) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Sentry.setContext(name, context);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Créer une transaction de performance
|
||
|
|
startTransaction(name: string, operation: string): Sentry.Transaction {
|
||
|
|
if (!this.isInitialized) {
|
||
|
|
return null as any;
|
||
|
|
}
|
||
|
|
|
||
|
|
return Sentry.startTransaction({
|
||
|
|
name,
|
||
|
|
op: operation,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ajouter une span à une transaction
|
||
|
|
addSpan(transaction: Sentry.Transaction, name: string, operation: string): Sentry.Span {
|
||
|
|
if (!this.isInitialized || !transaction) {
|
||
|
|
return null as any;
|
||
|
|
}
|
||
|
|
|
||
|
|
return transaction.startChild({
|
||
|
|
op: operation,
|
||
|
|
description: name,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Mesurer les performances d'une fonction
|
||
|
|
async measurePerformance<T>(
|
||
|
|
name: string,
|
||
|
|
operation: string,
|
||
|
|
fn: () => Promise<T>
|
||
|
|
): Promise<T> {
|
||
|
|
const transaction = this.startTransaction(name, operation);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const result = await fn();
|
||
|
|
transaction.setStatus('ok');
|
||
|
|
return result;
|
||
|
|
} catch (error) {
|
||
|
|
transaction.setStatus('error');
|
||
|
|
this.captureException(error as Error);
|
||
|
|
throw error;
|
||
|
|
} finally {
|
||
|
|
transaction.finish();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Mesurer les performances d'une fonction synchrone
|
||
|
|
measurePerformanceSync<T>(
|
||
|
|
name: string,
|
||
|
|
operation: string,
|
||
|
|
fn: () => T
|
||
|
|
): T {
|
||
|
|
const transaction = this.startTransaction(name, operation);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const result = fn();
|
||
|
|
transaction.setStatus('ok');
|
||
|
|
return result;
|
||
|
|
} catch (error) {
|
||
|
|
transaction.setStatus('error');
|
||
|
|
this.captureException(error as Error);
|
||
|
|
throw error;
|
||
|
|
} finally {
|
||
|
|
transaction.finish();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer les métriques de performance
|
||
|
|
capturePerformanceMetrics(metrics: {
|
||
|
|
name: string;
|
||
|
|
value: number;
|
||
|
|
unit: string;
|
||
|
|
tags?: Record<string, string>;
|
||
|
|
}): void {
|
||
|
|
if (!this.isInitialized) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Sentry.metrics.gauge(metrics.name, metrics.value, {
|
||
|
|
unit: metrics.unit,
|
||
|
|
tags: metrics.tags,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer les métriques de navigation
|
||
|
|
captureNavigationMetrics(route: string, duration: number): void {
|
||
|
|
this.capturePerformanceMetrics({
|
||
|
|
name: 'navigation.duration',
|
||
|
|
value: duration,
|
||
|
|
unit: 'millisecond',
|
||
|
|
tags: { route },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer les métriques d'API
|
||
|
|
captureAPIMetrics(endpoint: string, duration: number, status: number): void {
|
||
|
|
this.capturePerformanceMetrics({
|
||
|
|
name: 'api.request.duration',
|
||
|
|
value: duration,
|
||
|
|
unit: 'millisecond',
|
||
|
|
tags: { endpoint, status: status.toString() },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer les métriques de rendu
|
||
|
|
captureRenderMetrics(component: string, duration: number): void {
|
||
|
|
this.capturePerformanceMetrics({
|
||
|
|
name: 'render.duration',
|
||
|
|
value: duration,
|
||
|
|
unit: 'millisecond',
|
||
|
|
tags: { component },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer les métriques d'interaction
|
||
|
|
captureInteractionMetrics(action: string, duration: number): void {
|
||
|
|
this.capturePerformanceMetrics({
|
||
|
|
name: 'interaction.duration',
|
||
|
|
value: duration,
|
||
|
|
unit: 'millisecond',
|
||
|
|
tags: { action },
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer les erreurs de réseau
|
||
|
|
captureNetworkError(url: string, status: number, message: string): void {
|
||
|
|
this.captureException(new Error(`Network error: ${status} - ${message}`), {
|
||
|
|
url,
|
||
|
|
status,
|
||
|
|
type: 'network',
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer les erreurs de validation
|
||
|
|
captureValidationError(field: string, value: any, rule: string): void {
|
||
|
|
this.captureException(new Error(`Validation error: ${field} - ${rule}`), {
|
||
|
|
field,
|
||
|
|
value: String(value),
|
||
|
|
rule,
|
||
|
|
type: 'validation',
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer les erreurs d'authentification
|
||
|
|
captureAuthError(action: string, reason: string): void {
|
||
|
|
this.captureException(new Error(`Authentication error: ${action} - ${reason}`), {
|
||
|
|
action,
|
||
|
|
reason,
|
||
|
|
type: 'authentication',
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Capturer les erreurs de fonctionnalité
|
||
|
|
captureFeatureError(feature: string, action: string, error: Error): void {
|
||
|
|
this.captureException(error, {
|
||
|
|
feature,
|
||
|
|
action,
|
||
|
|
type: 'feature',
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Nettoyer le contexte
|
||
|
|
clearContext(): void {
|
||
|
|
if (!this.isInitialized) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Sentry.setUser(null);
|
||
|
|
Sentry.setContext('app', {});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fermer Sentry
|
||
|
|
close(): void {
|
||
|
|
if (!this.isInitialized) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Sentry.close();
|
||
|
|
this.isInitialized = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Instance singleton
|
||
|
|
export const monitoringService = MonitoringService.getInstance();
|
||
|
|
|
||
|
|
// Hook React pour le monitoring
|
||
|
|
export const useMonitoring = () => {
|
||
|
|
return {
|
||
|
|
captureException: monitoringService.captureException.bind(monitoringService),
|
||
|
|
captureMessage: monitoringService.captureMessage.bind(monitoringService),
|
||
|
|
setUser: monitoringService.setUser.bind(monitoringService),
|
||
|
|
setTag: monitoringService.setTag.bind(monitoringService),
|
||
|
|
setContext: monitoringService.setContext.bind(monitoringService),
|
||
|
|
measurePerformance: monitoringService.measurePerformance.bind(monitoringService),
|
||
|
|
measurePerformanceSync: monitoringService.measurePerformanceSync.bind(monitoringService),
|
||
|
|
};
|
||
|
|
};
|