657 lines
22 KiB
TypeScript
657 lines
22 KiB
TypeScript
/**
|
|
* Enhanced Global Error Handler for Maximum Developer Transparency
|
|
* Captures all uncaught errors, promise rejections, and React errors
|
|
*/
|
|
|
|
import { ApiError } from '../api/base/ApiError';
|
|
import { getGlobalErrorReporter } from './EnhancedErrorReporter';
|
|
import { ConsoleLogger } from './logging/ConsoleLogger';
|
|
import { getGlobalReplaySystem } from './ErrorReplay';
|
|
|
|
export interface GlobalErrorHandlerOptions {
|
|
/**
|
|
* Enable detailed error overlays in development
|
|
*/
|
|
showDevOverlay?: boolean;
|
|
|
|
/**
|
|
* Log all errors to console with maximum detail
|
|
*/
|
|
verboseLogging?: boolean;
|
|
|
|
/**
|
|
* Capture stack traces with enhanced context
|
|
*/
|
|
captureEnhancedStacks?: boolean;
|
|
|
|
/**
|
|
* Report to external services (Sentry, etc.)
|
|
*/
|
|
reportToExternal?: boolean;
|
|
|
|
/**
|
|
* Custom error filter to ignore certain errors
|
|
*/
|
|
errorFilter?: (error: Error) => boolean;
|
|
}
|
|
|
|
export class GlobalErrorHandler {
|
|
private options: GlobalErrorHandlerOptions;
|
|
private logger: ConsoleLogger;
|
|
private errorReporter: ReturnType<typeof getGlobalErrorReporter>;
|
|
private errorHistory: Array<{
|
|
error: Error | ApiError;
|
|
timestamp: string;
|
|
context?: unknown;
|
|
stackEnhanced?: string;
|
|
}> = [];
|
|
private readonly MAX_HISTORY = 100;
|
|
private isInitialized = false;
|
|
|
|
constructor(options: GlobalErrorHandlerOptions = {}) {
|
|
this.options = {
|
|
showDevOverlay: options.showDevOverlay ?? process.env.NODE_ENV === 'development',
|
|
verboseLogging: options.verboseLogging ?? process.env.NODE_ENV === 'development',
|
|
captureEnhancedStacks: options.captureEnhancedStacks ?? process.env.NODE_ENV === 'development',
|
|
reportToExternal: options.reportToExternal ?? process.env.NODE_ENV === 'production',
|
|
errorFilter: options.errorFilter,
|
|
};
|
|
|
|
this.logger = new ConsoleLogger();
|
|
this.errorReporter = getGlobalErrorReporter();
|
|
}
|
|
|
|
/**
|
|
* Initialize global error handlers
|
|
*/
|
|
initialize(): void {
|
|
if (this.isInitialized) {
|
|
console.warn('[GlobalErrorHandler] Already initialized');
|
|
return;
|
|
}
|
|
|
|
// Handle uncaught JavaScript errors
|
|
window.addEventListener('error', this.handleWindowError);
|
|
|
|
// Handle unhandled promise rejections
|
|
window.addEventListener('unhandledrejection', this.handleUnhandledRejection);
|
|
|
|
// Override console.error to capture framework errors
|
|
this.overrideConsoleError();
|
|
|
|
// React error boundary fallback
|
|
this.setupReactErrorHandling();
|
|
|
|
this.isInitialized = true;
|
|
|
|
if (this.options.verboseLogging) {
|
|
this.logger.info('Global error handler initialized', {
|
|
devOverlay: this.options.showDevOverlay,
|
|
verboseLogging: this.options.verboseLogging,
|
|
enhancedStacks: this.options.captureEnhancedStacks,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle window errors (uncaught JavaScript errors)
|
|
*/
|
|
private handleWindowError = (event: ErrorEvent): void => {
|
|
const error = event.error;
|
|
|
|
// Apply error filter if provided
|
|
if (this.options.errorFilter && !this.options.errorFilter(error)) {
|
|
return;
|
|
}
|
|
|
|
const enhancedContext = this.captureEnhancedContext('window_error', {
|
|
filename: event.filename,
|
|
lineno: event.lineno,
|
|
colno: event.colno,
|
|
message: event.message,
|
|
});
|
|
|
|
// Log with maximum detail
|
|
this.logErrorWithMaximumDetail(error, enhancedContext);
|
|
|
|
// Store in history
|
|
this.addToHistory(error, enhancedContext);
|
|
|
|
// Show dev overlay if enabled
|
|
if (this.options.showDevOverlay) {
|
|
this.showDevOverlay(error, enhancedContext);
|
|
}
|
|
|
|
// Report to external if enabled
|
|
if (this.options.reportToExternal) {
|
|
this.reportToExternal(error, enhancedContext);
|
|
}
|
|
|
|
// Auto-capture for replay in development
|
|
if (this.options.showDevOverlay) {
|
|
const replaySystem = getGlobalReplaySystem();
|
|
replaySystem.autoCapture(error, enhancedContext);
|
|
}
|
|
|
|
// Prevent default error logging in dev to avoid duplicates
|
|
if (this.options.showDevOverlay) {
|
|
event.preventDefault();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handle unhandled promise rejections
|
|
*/
|
|
private handleUnhandledRejection = (event: PromiseRejectionEvent): void => {
|
|
const error = event.reason;
|
|
|
|
// Apply error filter if provided
|
|
if (this.options.errorFilter && !this.options.errorFilter(error)) {
|
|
return;
|
|
}
|
|
|
|
const enhancedContext = this.captureEnhancedContext('unhandled_promise', {
|
|
promise: event.promise,
|
|
reason: typeof error === 'string' ? error : error?.message || 'Unknown promise rejection',
|
|
});
|
|
|
|
// Log with maximum detail
|
|
this.logErrorWithMaximumDetail(error, enhancedContext);
|
|
|
|
// Store in history
|
|
this.addToHistory(error, enhancedContext);
|
|
|
|
// Show dev overlay if enabled
|
|
if (this.options.showDevOverlay) {
|
|
this.showDevOverlay(error, enhancedContext);
|
|
}
|
|
|
|
// Report to external if enabled
|
|
if (this.options.reportToExternal) {
|
|
this.reportToExternal(error, enhancedContext);
|
|
}
|
|
|
|
// Prevent default logging
|
|
if (this.options.showDevOverlay) {
|
|
event.preventDefault();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Override console.error to capture framework errors
|
|
*/
|
|
private overrideConsoleError(): void {
|
|
const originalError = console.error;
|
|
|
|
console.error = (...args: unknown[]): void => {
|
|
// Call original first
|
|
originalError.apply(console, args);
|
|
|
|
// Try to extract error from arguments
|
|
const error = args.find(arg => arg instanceof Error) ||
|
|
args.find(arg => typeof arg === 'object' && arg !== null && 'message' in arg) ||
|
|
new Error(args.map(a => String(a)).join(' '));
|
|
|
|
if (error instanceof Error) {
|
|
const enhancedContext = this.captureEnhancedContext('console_error', {
|
|
originalArgs: args,
|
|
});
|
|
|
|
// Store in history
|
|
this.addToHistory(error, enhancedContext);
|
|
|
|
// Show dev overlay if enabled
|
|
if (this.options.showDevOverlay) {
|
|
this.showDevOverlay(error, enhancedContext);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Setup React-specific error handling
|
|
*/
|
|
private setupReactErrorHandling(): void {
|
|
// This will be used by React Error Boundaries
|
|
// We'll provide a global registry for React errors
|
|
(window as any).__GRIDPILOT_REACT_ERRORS__ = [];
|
|
}
|
|
|
|
/**
|
|
* Capture enhanced context with stack trace and environment info
|
|
*/
|
|
private captureEnhancedContext(type: string, additionalContext: Record<string, unknown> = {}): Record<string, unknown> {
|
|
const stack = new Error().stack;
|
|
|
|
return {
|
|
type,
|
|
timestamp: new Date().toISOString(),
|
|
userAgent: navigator.userAgent,
|
|
url: window.location.href,
|
|
language: navigator.language,
|
|
platform: navigator.platform,
|
|
viewport: {
|
|
width: window.innerWidth,
|
|
height: window.innerHeight,
|
|
},
|
|
screen: {
|
|
width: window.screen.width,
|
|
height: window.screen.height,
|
|
},
|
|
memory: (performance as any).memory ? {
|
|
usedJSHeapSize: (performance as any).memory.usedJSHeapSize,
|
|
totalJSHeapSize: (performance as any).memory.totalJSHeapSize,
|
|
} : null,
|
|
connection: (navigator as any).connection ? {
|
|
effectiveType: (navigator as any).connection.effectiveType,
|
|
downlink: (navigator as any).connection.downlink,
|
|
rtt: (navigator as any).connection.rtt,
|
|
} : null,
|
|
...additionalContext,
|
|
enhancedStack: this.options.captureEnhancedStacks ? this.enhanceStackTrace(stack) : undefined,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Enhance stack trace with additional context
|
|
*/
|
|
private enhanceStackTrace(stack?: string): string | undefined {
|
|
if (!stack) return undefined;
|
|
|
|
// Add source map information if available
|
|
const lines = stack.split('\n').slice(1); // Remove first line (error message)
|
|
|
|
const enhanced = lines.map(line => {
|
|
// Try to extract file and line info
|
|
const match = line.match(/at (.+) \((.+):(\d+):(\d+)\)/) ||
|
|
line.match(/at (.+):(\d+):(\d+)/);
|
|
|
|
if (match) {
|
|
const func = match[1] || 'anonymous';
|
|
const file = match[2] || match[1];
|
|
const lineNum = match[3] || match[2];
|
|
const colNum = match[4] || match[3];
|
|
|
|
// Add source map comment if in development
|
|
if (process.env.NODE_ENV === 'development' && file.includes('.js')) {
|
|
return `at ${func} (${file}:${lineNum}:${colNum}) [Source Map: ${file}.map]`;
|
|
}
|
|
|
|
return `at ${func} (${file}:${lineNum}:${colNum})`;
|
|
}
|
|
|
|
return line.trim();
|
|
});
|
|
|
|
return enhanced.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Log error with maximum detail
|
|
*/
|
|
private logErrorWithMaximumDetail(error: Error | ApiError, context: Record<string, unknown>): void {
|
|
if (!this.options.verboseLogging) return;
|
|
|
|
const isApiError = error instanceof ApiError;
|
|
|
|
// Group all related information
|
|
console.groupCollapsed(`%c[GLOBAL ERROR] ${error.name || 'Error'}: ${error.message}`,
|
|
'color: #ff4444; font-weight: bold; font-size: 14px;'
|
|
);
|
|
|
|
// Error details
|
|
console.log('Error Details:', {
|
|
name: error.name,
|
|
message: error.message,
|
|
stack: error.stack,
|
|
type: isApiError ? error.type : 'N/A',
|
|
severity: isApiError ? error.getSeverity() : 'error',
|
|
retryable: isApiError ? error.isRetryable() : 'N/A',
|
|
connectivity: isApiError ? error.isConnectivityIssue() : 'N/A',
|
|
});
|
|
|
|
// Context information
|
|
console.log('Context:', context);
|
|
|
|
// Enhanced stack trace
|
|
if (context.enhancedStack) {
|
|
console.log('Enhanced Stack Trace:\n' + context.enhancedStack);
|
|
}
|
|
|
|
// API-specific information
|
|
if (isApiError && error.context) {
|
|
console.log('API Context:', error.context);
|
|
}
|
|
|
|
// Environment information
|
|
console.log('Environment:', {
|
|
mode: process.env.NODE_ENV,
|
|
nextPublicMode: process.env.NEXT_PUBLIC_GRIDPILOT_MODE,
|
|
version: process.env.NEXT_PUBLIC_APP_VERSION,
|
|
buildTime: process.env.NEXT_PUBLIC_BUILD_TIME,
|
|
});
|
|
|
|
// Performance metrics
|
|
if (context.memory && typeof context.memory === 'object' &&
|
|
'usedJSHeapSize' in context.memory && 'totalJSHeapSize' in context.memory) {
|
|
const memory = context.memory as { usedJSHeapSize: number; totalJSHeapSize: number };
|
|
console.log('Memory Usage:', {
|
|
used: `${(memory.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
|
|
total: `${(memory.totalJSHeapSize / 1024 / 1024).toFixed(2)} MB`,
|
|
});
|
|
}
|
|
|
|
// Network information
|
|
if (context.connection) {
|
|
console.log('Network:', context.connection);
|
|
}
|
|
|
|
// Error history (last 5 errors)
|
|
if (this.errorHistory.length > 0) {
|
|
console.log('Recent Error History:', this.errorHistory.slice(-5));
|
|
}
|
|
|
|
console.groupEnd();
|
|
|
|
// Also log to our logger
|
|
this.logger.error(error.message, error, context);
|
|
}
|
|
|
|
/**
|
|
* Show development overlay with error details
|
|
*/
|
|
private showDevOverlay(error: Error | ApiError, context: Record<string, unknown>): void {
|
|
// Check if overlay already exists
|
|
const existingOverlay = document.getElementById('gridpilot-error-overlay');
|
|
if (existingOverlay) {
|
|
// Update existing overlay
|
|
this.updateDevOverlay(existingOverlay, error, context);
|
|
return;
|
|
}
|
|
|
|
// Create new overlay
|
|
const overlay = document.createElement('div');
|
|
overlay.id = 'gridpilot-error-overlay';
|
|
overlay.style.cssText = `
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.95);
|
|
color: #fff;
|
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
font-size: 12px;
|
|
z-index: 999999;
|
|
overflow: auto;
|
|
padding: 20px;
|
|
border: 4px solid #ff4444;
|
|
box-shadow: 0 0 50px rgba(255, 68, 68, 0.5);
|
|
`;
|
|
|
|
this.updateDevOverlay(overlay, error, context);
|
|
document.body.appendChild(overlay);
|
|
|
|
// Add keyboard shortcut to dismiss
|
|
const dismissHandler = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape' || e.key === 'Enter') {
|
|
overlay.remove();
|
|
document.removeEventListener('keydown', dismissHandler);
|
|
}
|
|
};
|
|
document.addEventListener('keydown', dismissHandler);
|
|
}
|
|
|
|
/**
|
|
* Update existing dev overlay
|
|
*/
|
|
private updateDevOverlay(overlay: HTMLElement, error: Error | ApiError, context: Record<string, unknown>): void {
|
|
const isApiError = error instanceof ApiError;
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
|
|
overlay.innerHTML = `
|
|
<div style="max-width: 1200px; margin: 0 auto;">
|
|
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 20px;">
|
|
<div>
|
|
<h1 style="color: #ff4444; margin: 0 0 10px 0; font-size: 24px;">
|
|
🚨 UNCAUGHT ERROR - DEVELOPMENT MODE
|
|
</h1>
|
|
<div style="color: #888;">${timestamp} | Press ESC or ENTER to dismiss</div>
|
|
</div>
|
|
<button onclick="this.parentElement.parentElement.remove()"
|
|
style="background: #ff4444; color: white; border: none; padding: 8px 16px; cursor: pointer; border-radius: 4px; font-weight: bold;">
|
|
CLOSE
|
|
</button>
|
|
</div>
|
|
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
|
|
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333;">
|
|
<h3 style="color: #ffaa00; margin-top: 0;">Error Information</h3>
|
|
<div style="line-height: 1.6;">
|
|
<div><strong>Type:</strong> <span style="color: #ff4444;">${isApiError ? error.type : error.name}</span></div>
|
|
<div><strong>Message:</strong> ${error.message}</div>
|
|
${isApiError ? `<div><strong>Severity:</strong> ${error.getSeverity()}</div>` : ''}
|
|
${isApiError ? `<div><strong>Retryable:</strong> ${error.isRetryable()}</div>` : ''}
|
|
${isApiError ? `<div><strong>Connectivity:</strong> ${error.isConnectivityIssue()}</div>` : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333;">
|
|
<h3 style="color: #00aaff; margin-top: 0;">Environment</h3>
|
|
<div style="line-height: 1.6;">
|
|
<div><strong>Mode:</strong> ${process.env.NODE_ENV}</div>
|
|
<div><strong>App Mode:</strong> ${process.env.NEXT_PUBLIC_GRIDPILOT_MODE || 'pre-launch'}</div>
|
|
<div><strong>URL:</strong> ${window.location.href}</div>
|
|
<div><strong>User Agent:</strong> ${navigator.userAgent}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
${isApiError && error.context ? `
|
|
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333; margin-bottom: 20px;">
|
|
<h3 style="color: #00ff88; margin-top: 0;">API Context</h3>
|
|
<pre style="background: #000; padding: 10px; border-radius: 4px; overflow-x: auto; margin: 0;">${JSON.stringify(error.context, null, 2)}</pre>
|
|
</div>
|
|
` : ''}
|
|
|
|
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333; margin-bottom: 20px;">
|
|
<h3 style="color: #ff4444; margin-top: 0;">Stack Trace</h3>
|
|
<pre style="background: #000; padding: 10px; border-radius: 4px; overflow-x: auto; margin: 0; white-space: pre-wrap;">${context.enhancedStack || error.stack || 'No stack trace available'}</pre>
|
|
</div>
|
|
|
|
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333; margin-bottom: 20px;">
|
|
<h3 style="color: #ffaa00; margin-top: 0;">Additional Context</h3>
|
|
<pre style="background: #000; padding: 10px; border-radius: 4px; overflow-x: auto; margin: 0;">${JSON.stringify(context, null, 2)}</pre>
|
|
</div>
|
|
|
|
<div style="background: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333;">
|
|
<h3 style="color: #00aaff; margin-top: 0;">Quick Actions</h3>
|
|
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
|
<button onclick="navigator.clipboard.writeText(\`${error.message}\n\nStack:\n${error.stack}\n\nContext:\n${JSON.stringify(context, null, 2)}\`)"
|
|
style="background: #0066cc; color: white; border: none; padding: 8px 12px; cursor: pointer; border-radius: 4px;">
|
|
📋 Copy Error Details
|
|
</button>
|
|
<button onclick="window.location.reload()"
|
|
style="background: #cc6600; color: white; border: none; padding: 8px 12px; cursor: pointer; border-radius: 4px;">
|
|
🔄 Reload Page
|
|
</button>
|
|
<button onclick="console.clear(); console.log('Error details:', ${JSON.stringify({ error: error.message, stack: error.stack, context }).replace(/"/g, '"')})"
|
|
style="background: #6600cc; color: white; border: none; padding: 8px 12px; cursor: pointer; border-radius: 4px;">
|
|
📝 Log to Console
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-top: 20px; padding: 15px; background: #222; border-radius: 4px; border-left: 4px solid #ffaa00;">
|
|
<strong>💡 Tip:</strong> This overlay only appears in development mode. In production, errors are logged silently and handled gracefully.
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Report error to external services
|
|
*/
|
|
private reportToExternal(error: Error | ApiError, context: Record<string, unknown>): void {
|
|
// This is a placeholder for external error reporting (Sentry, LogRocket, etc.)
|
|
// In a real implementation, you would send structured data to your error tracking service
|
|
|
|
if (this.options.verboseLogging) {
|
|
console.log('[EXTERNAL REPORT] Would send to error tracking service:', {
|
|
error: {
|
|
name: error.name,
|
|
message: error.message,
|
|
stack: error.stack,
|
|
type: error instanceof ApiError ? error.type : undefined,
|
|
},
|
|
context,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add error to history
|
|
*/
|
|
private addToHistory(error: Error | ApiError, context: Record<string, unknown>): void {
|
|
const entry = {
|
|
error,
|
|
timestamp: new Date().toISOString(),
|
|
context,
|
|
stackEnhanced: context.enhancedStack as string | undefined,
|
|
};
|
|
|
|
this.errorHistory.push(entry);
|
|
|
|
// Keep only last N errors
|
|
if (this.errorHistory.length > this.MAX_HISTORY) {
|
|
this.errorHistory = this.errorHistory.slice(-this.MAX_HISTORY);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get error history
|
|
*/
|
|
getErrorHistory(): Array<{ error: Error | ApiError; timestamp: string; context?: unknown; stackEnhanced?: string }> {
|
|
return [...this.errorHistory];
|
|
}
|
|
|
|
/**
|
|
* Clear error history
|
|
*/
|
|
clearHistory(): void {
|
|
this.errorHistory = [];
|
|
if (this.options.verboseLogging) {
|
|
this.logger.info('Error history cleared');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get statistics about errors
|
|
*/
|
|
getStats(): {
|
|
total: number;
|
|
byType: Record<string, number>;
|
|
recent: Array<{ timestamp: string; message: string; type: string }>;
|
|
} {
|
|
const stats = {
|
|
total: this.errorHistory.length,
|
|
byType: {} as Record<string, number>,
|
|
recent: this.errorHistory.slice(-10).map(entry => ({
|
|
timestamp: entry.timestamp,
|
|
message: entry.error.message,
|
|
type: entry.error instanceof ApiError ? entry.error.type : entry.error.name || 'Error',
|
|
})),
|
|
};
|
|
|
|
this.errorHistory.forEach(entry => {
|
|
const type = entry.error instanceof ApiError ? entry.error.type : entry.error.name || 'Error';
|
|
stats.byType[type] = (stats.byType[type] || 0) + 1;
|
|
});
|
|
|
|
return stats;
|
|
}
|
|
|
|
/**
|
|
* Manually report an error
|
|
*/
|
|
report(error: Error | ApiError, additionalContext: Record<string, unknown> = {}): void {
|
|
const context = this.captureEnhancedContext('manual_report', additionalContext);
|
|
|
|
this.logErrorWithMaximumDetail(error, context);
|
|
this.addToHistory(error, context);
|
|
|
|
if (this.options.showDevOverlay) {
|
|
this.showDevOverlay(error, context);
|
|
|
|
// Auto-capture for replay
|
|
const replaySystem = getGlobalReplaySystem();
|
|
replaySystem.autoCapture(error, context);
|
|
}
|
|
|
|
if (this.options.reportToExternal) {
|
|
this.reportToExternal(error, context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroy the error handler and remove all listeners
|
|
*/
|
|
destroy(): void {
|
|
window.removeEventListener('error', this.handleWindowError);
|
|
window.removeEventListener('unhandledrejection', this.handleUnhandledRejection);
|
|
|
|
// Restore original console.error
|
|
if ((console as any)._originalError) {
|
|
console.error = (console as any)._originalError;
|
|
}
|
|
|
|
// Remove overlay if exists
|
|
const overlay = document.getElementById('gridpilot-error-overlay');
|
|
if (overlay) {
|
|
overlay.remove();
|
|
}
|
|
|
|
this.isInitialized = false;
|
|
|
|
if (this.options.verboseLogging) {
|
|
this.logger.info('Global error handler destroyed');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Global instance accessor
|
|
*/
|
|
let globalErrorHandlerInstance: GlobalErrorHandler | null = null;
|
|
|
|
export function getGlobalErrorHandler(): GlobalErrorHandler {
|
|
if (!globalErrorHandlerInstance) {
|
|
globalErrorHandlerInstance = new GlobalErrorHandler();
|
|
}
|
|
return globalErrorHandlerInstance;
|
|
}
|
|
|
|
/**
|
|
* Initialize global error handling
|
|
*/
|
|
export function initializeGlobalErrorHandling(options?: GlobalErrorHandlerOptions): GlobalErrorHandler {
|
|
const handler = new GlobalErrorHandler(options);
|
|
handler.initialize();
|
|
globalErrorHandlerInstance = handler;
|
|
return handler;
|
|
}
|
|
|
|
/**
|
|
* React hook for manual error reporting
|
|
*/
|
|
export function useGlobalErrorHandler() {
|
|
const handler = getGlobalErrorHandler();
|
|
|
|
return {
|
|
report: (error: Error | ApiError, context?: Record<string, unknown>) => handler.report(error, context),
|
|
getHistory: () => handler.getErrorHistory(),
|
|
getStats: () => handler.getStats(),
|
|
clearHistory: () => handler.clearHistory(),
|
|
};
|
|
} |