335 lines
9.6 KiB
TypeScript
335 lines
9.6 KiB
TypeScript
/**
|
|
* Enhanced Error Reporter with user notifications and environment-specific handling
|
|
*/
|
|
|
|
import { ErrorReporter } from '../interfaces/ErrorReporter';
|
|
import { Logger } from '../interfaces/Logger';
|
|
import { ApiError } from '../api/base/ApiError';
|
|
import { connectionMonitor } from '../api/base/ApiConnectionMonitor';
|
|
|
|
// Import notification system (will be used if available)
|
|
let notificationSystem: any = null;
|
|
try {
|
|
// Dynamically import to avoid circular dependencies
|
|
import('@/components/notifications/NotificationProvider').then(module => {
|
|
notificationSystem = module;
|
|
}).catch(() => {
|
|
// Notification system not available yet
|
|
});
|
|
} catch {
|
|
// Silent fail - notification system may not be initialized
|
|
}
|
|
|
|
export interface EnhancedErrorReporterOptions {
|
|
/**
|
|
* Whether to show user-facing notifications
|
|
*/
|
|
showUserNotifications?: boolean;
|
|
|
|
/**
|
|
* Whether to log to console (always true in dev)
|
|
*/
|
|
logToConsole?: boolean;
|
|
|
|
/**
|
|
* Whether to report to external service (e.g., Sentry)
|
|
*/
|
|
reportToExternal?: boolean;
|
|
|
|
/**
|
|
* Custom error handler for specific error types
|
|
*/
|
|
customHandlers?: Record<string, (error: ApiError) => void>;
|
|
}
|
|
|
|
export class EnhancedErrorReporter implements ErrorReporter {
|
|
private options: EnhancedErrorReporterOptions;
|
|
private logger: Logger;
|
|
private errorBuffer: Array<{ error: ApiError; context: unknown }> = [];
|
|
private readonly MAX_BUFFER_SIZE = 50;
|
|
|
|
constructor(logger: Logger, options: EnhancedErrorReporterOptions = {}) {
|
|
this.logger = logger;
|
|
this.options = {
|
|
showUserNotifications: options.showUserNotifications ?? true,
|
|
logToConsole: options.logToConsole ?? true,
|
|
reportToExternal: options.reportToExternal ?? false,
|
|
customHandlers: options.customHandlers || {},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Main error reporting method
|
|
*/
|
|
report(error: Error, context?: unknown): void {
|
|
// Only handle ApiError instances for enhanced reporting
|
|
if (!(error instanceof ApiError)) {
|
|
// For non-API errors, use basic logging
|
|
if (this.options.logToConsole) {
|
|
console.error('Non-API Error:', error, context);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Add to buffer for potential batch reporting
|
|
this.addToBuffer(error, context);
|
|
|
|
// Log based on environment and severity
|
|
this.logError(error, context);
|
|
|
|
// Handle custom error types
|
|
this.handleCustomHandlers(error);
|
|
|
|
// Show user notifications if enabled
|
|
if (this.options.showUserNotifications) {
|
|
this.showUserNotification(error);
|
|
}
|
|
|
|
// Report to external services if configured
|
|
if (this.options.reportToExternal) {
|
|
this.reportToExternal(error, context);
|
|
}
|
|
|
|
// Update connection monitor
|
|
if (error.isConnectivityIssue()) {
|
|
connectionMonitor.recordFailure(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log error with appropriate severity
|
|
*/
|
|
private logError(error: ApiError, context: unknown): void {
|
|
if (!this.options.logToConsole) return;
|
|
|
|
const isDev = process.env.NODE_ENV === 'development';
|
|
const severity = error.getSeverity();
|
|
|
|
const message = isDev ? error.getDeveloperMessage() : error.getUserMessage();
|
|
const logContext = {
|
|
...error.context,
|
|
...context,
|
|
type: error.type,
|
|
isRetryable: error.isRetryable(),
|
|
isConnectivity: error.isConnectivityIssue(),
|
|
};
|
|
|
|
if (severity === 'error') {
|
|
this.logger.error(message, error, logContext);
|
|
if (isDev) {
|
|
console.error(`[API-ERROR] ${message}`, { error, context: logContext });
|
|
}
|
|
} else if (severity === 'warn') {
|
|
this.logger.warn(message, logContext);
|
|
if (isDev) {
|
|
console.warn(`[API-WARN] ${message}`, { context: logContext });
|
|
}
|
|
} else {
|
|
this.logger.info(message, logContext);
|
|
if (isDev) {
|
|
console.log(`[API-INFO] ${message}`, { context: logContext });
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show user-facing notification
|
|
*/
|
|
private showUserNotification(error: ApiError): void {
|
|
const isDev = process.env.NODE_ENV === 'development';
|
|
|
|
// In development, we might want to show more details
|
|
if (isDev) {
|
|
// Use console notification in dev
|
|
console.log(`%c[USER-NOTIFICATION] ${error.getUserMessage()}`,
|
|
'background: #222; color: #bada55; padding: 4px 8px; border-radius: 4px;'
|
|
);
|
|
return;
|
|
}
|
|
|
|
// In production, use the notification system if available
|
|
// This is a deferred import to avoid circular dependencies
|
|
if (typeof window !== 'undefined') {
|
|
setTimeout(() => {
|
|
try {
|
|
// Try to access notification context if available
|
|
const notificationEvent = new CustomEvent('gridpilot-notification', {
|
|
detail: {
|
|
type: 'error',
|
|
title: this.getNotificationTitle(error),
|
|
message: error.getUserMessage(),
|
|
variant: error.isConnectivityIssue() ? 'modal' : 'toast',
|
|
autoDismiss: !error.isConnectivityIssue(),
|
|
}
|
|
});
|
|
window.dispatchEvent(notificationEvent);
|
|
} catch (e) {
|
|
// Fallback to browser alert if notification system unavailable
|
|
if (error.isConnectivityIssue()) {
|
|
console.warn('API Error:', error.getUserMessage());
|
|
}
|
|
}
|
|
}, 100);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get appropriate notification title
|
|
*/
|
|
private getNotificationTitle(error: ApiError): string {
|
|
switch (error.type) {
|
|
case 'NETWORK_ERROR':
|
|
return 'Connection Lost';
|
|
case 'TIMEOUT_ERROR':
|
|
return 'Request Timed Out';
|
|
case 'AUTH_ERROR':
|
|
return 'Authentication Required';
|
|
case 'SERVER_ERROR':
|
|
return 'Server Error';
|
|
case 'RATE_LIMIT_ERROR':
|
|
return 'Rate Limit Reached';
|
|
default:
|
|
return 'Something Went Wrong';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle custom error type handlers
|
|
*/
|
|
private handleCustomHandlers(error: ApiError): void {
|
|
if (this.options.customHandlers && this.options.customHandlers[error.type]) {
|
|
try {
|
|
this.options.customHandlers[error.type]!(error);
|
|
} catch (handlerError) {
|
|
console.error('Custom error handler failed:', handlerError);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Report to external services (placeholder for future integration)
|
|
*/
|
|
private reportToExternal(error: ApiError, context: unknown): void {
|
|
// Placeholder for external error reporting (e.g., Sentry, LogRocket)
|
|
// In a real implementation, this would send to your error tracking service
|
|
|
|
if (process.env.NODE_ENV === 'development') {
|
|
const contextObj = typeof context === 'object' && context !== null ? context : {};
|
|
console.log('[EXTERNAL-REPORT] Would report:', {
|
|
type: error.type,
|
|
message: error.message,
|
|
context: { ...error.context, ...contextObj },
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add error to buffer for potential batch reporting
|
|
*/
|
|
private addToBuffer(error: ApiError, context: unknown): void {
|
|
this.errorBuffer.push({ error, context });
|
|
|
|
// Keep buffer size in check
|
|
if (this.errorBuffer.length > this.MAX_BUFFER_SIZE) {
|
|
this.errorBuffer.shift();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get buffered errors
|
|
*/
|
|
getBufferedErrors(): Array<{ error: ApiError; context: unknown }> {
|
|
return [...this.errorBuffer];
|
|
}
|
|
|
|
/**
|
|
* Clear error buffer
|
|
*/
|
|
clearBuffer(): void {
|
|
this.errorBuffer = [];
|
|
}
|
|
|
|
/**
|
|
* Batch report buffered errors
|
|
*/
|
|
flush(): void {
|
|
if (this.errorBuffer.length === 0) return;
|
|
|
|
if (this.options.logToConsole) {
|
|
console.groupCollapsed(`[API-REPORT] Flushing ${this.errorBuffer.length} buffered errors`);
|
|
this.errorBuffer.forEach(({ error, context }) => {
|
|
console.log(`${error.type}: ${error.message}`, { error, context });
|
|
});
|
|
console.groupEnd();
|
|
}
|
|
|
|
// In production, this would batch send to external service
|
|
if (this.options.reportToExternal) {
|
|
const batch = this.errorBuffer.map(({ error, context }) => {
|
|
const contextObj = typeof context === 'object' && context !== null ? context : {};
|
|
return {
|
|
type: error.type,
|
|
message: error.message,
|
|
context: { ...error.context, ...contextObj },
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
});
|
|
|
|
console.log('[EXTERNAL-REPORT] Batch:', batch);
|
|
}
|
|
|
|
this.clearBuffer();
|
|
}
|
|
|
|
/**
|
|
* Update options dynamically
|
|
*/
|
|
updateOptions(newOptions: Partial<EnhancedErrorReporterOptions>): void {
|
|
this.options = { ...this.options, ...newOptions };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Global error reporter instance
|
|
*/
|
|
let globalReporter: EnhancedErrorReporter | null = null;
|
|
|
|
export function getGlobalErrorReporter(): EnhancedErrorReporter {
|
|
if (!globalReporter) {
|
|
// Import the console logger
|
|
const { ConsoleLogger } = require('./ConsoleLogger');
|
|
globalReporter = new EnhancedErrorReporter(new ConsoleLogger(), {
|
|
showUserNotifications: true,
|
|
logToConsole: true,
|
|
reportToExternal: process.env.NODE_ENV === 'production',
|
|
});
|
|
}
|
|
return globalReporter;
|
|
}
|
|
|
|
/**
|
|
* Helper function to report API errors easily
|
|
*/
|
|
export function reportApiError(
|
|
error: ApiError,
|
|
context?: unknown,
|
|
reporter?: EnhancedErrorReporter
|
|
): void {
|
|
const rep = reporter || getGlobalErrorReporter();
|
|
rep.report(error, context);
|
|
}
|
|
|
|
/**
|
|
* React hook for error reporting
|
|
*/
|
|
export function useErrorReporter() {
|
|
const reporter = getGlobalErrorReporter();
|
|
|
|
return {
|
|
report: (error: Error, context?: unknown) => reporter.report(error, context),
|
|
flush: () => reporter.flush(),
|
|
getBuffered: () => reporter.getBufferedErrors(),
|
|
updateOptions: (opts: Partial<EnhancedErrorReporterOptions>) => reporter.updateOptions(opts),
|
|
};
|
|
} |