dev setup
This commit is contained in:
335
apps/website/lib/infrastructure/EnhancedErrorReporter.ts
Normal file
335
apps/website/lib/infrastructure/EnhancedErrorReporter.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
* 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),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user