veza/apps/web/src/lib/sentry.ts

145 lines
3.8 KiB
TypeScript

/**
* FIX #20: Configuration Sentry pour error tracking
* - Intégration avec le logger structuré
* - Capture automatique des erreurs React
* - Enrichissement avec contexte (request_id, user_id, etc.)
*/
import * as Sentry from '@sentry/react';
import { logger, getLogContext } from '@/utils/logger';
/**
* Initialiser Sentry avec configuration
*/
export function initSentry(): void {
const dsn = import.meta.env.VITE_SENTRY_DSN;
const environment = import.meta.env.MODE || 'development';
const enabled = import.meta.env.PROD && dsn; // Activer uniquement en production si DSN configuré
if (!enabled) {
if (environment !== 'development' && !dsn) {
logger.debug('[Sentry] Error tracking disabled', {
reason: 'DSN not configured',
environment,
});
}
return;
}
Sentry.init({
dsn,
environment,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration({
maskAllText: true,
blockAllMedia: true,
}),
],
// Performance Monitoring
tracesSampleRate: environment === 'production' ? 0.1 : 1.0, // 10% en prod, 100% en dev
// Session Replay
replaysSessionSampleRate: 0.1, // 10% des sessions
replaysOnErrorSampleRate: 1.0, // 100% des sessions avec erreur
// Filtrage des erreurs
beforeSend(event, hint) {
// Enrichir avec le contexte du logger
const logContext = getLogContext();
if (logContext.request_id) {
event.tags = { ...event.tags, request_id: logContext.request_id };
}
if (logContext.user_id) {
event.user = { ...event.user, id: String(logContext.user_id) };
}
// Logger l'erreur avec le logger structuré
const error = hint.originalException;
logger.error('[Sentry] Error captured', {
error: error instanceof Error ? error.message : String(error),
sentry_event_id: event.event_id,
...logContext,
});
return event;
},
// Ignorer certaines erreurs
ignoreErrors: [
// Erreurs réseau courantes
'NetworkError',
'Network request failed',
'Failed to fetch',
// Erreurs de résolution DNS
'Resolving timed out',
// Erreurs de CORS
'CORS',
// Erreurs de script tiers
'Script error',
'Non-Error promise rejection captured',
],
// Ignorer certaines URLs
denyUrls: [
// Extensions de navigateur
/extensions\//i,
/^chrome:\/\//i,
/^chrome-extension:\/\//i,
// Scripts tiers
/cdn\./i,
],
});
logger.info('[Sentry] Error tracking initialized', {
environment,
dsn_configured: !!dsn,
});
}
/**
* Enrichir le contexte Sentry avec les informations du logger
*/
export function setSentryContext(context: Record<string, unknown>): void {
Sentry.setContext('application', context);
}
/**
* Capturer une exception manuellement
*/
export function captureException(
error: Error,
context?: Record<string, unknown>,
): string {
if (context) {
Sentry.setContext('custom', context);
}
// Enrichir avec le contexte du logger
const logContext = getLogContext();
if (logContext.request_id) {
Sentry.setTag('request_id', String(logContext.request_id));
}
if (logContext.user_id) {
Sentry.setUser({ id: String(logContext.user_id) });
}
return Sentry.captureException(error);
}
/**
* Capturer un message personnalisé
*/
export function captureMessage(
message: string,
level: Sentry.SeverityLevel = 'info',
context?: Record<string, unknown>,
): string {
if (context) {
Sentry.setContext('custom', context);
}
// Enrichir avec le contexte du logger
const logContext = getLogContext();
if (logContext.request_id) {
Sentry.setTag('request_id', String(logContext.request_id));
}
return Sentry.captureMessage(message, level);
}