195 lines
5.1 KiB
TypeScript
195 lines
5.1 KiB
TypeScript
import type {
|
|
CustomAPIConfig,
|
|
ErrorManagerConfig,
|
|
ErrorReport,
|
|
ErrorReporter,
|
|
SentryConfig,
|
|
} from './ErrorManager.types';
|
|
|
|
class ErrorManager {
|
|
private config: ErrorManagerConfig = { enabled: true };
|
|
private reporters: ErrorReporter[] = [];
|
|
private sentryInstance: any = null;
|
|
|
|
configure(config: ErrorManagerConfig) {
|
|
this.config = { ...this.config, ...config };
|
|
this.reporters = [];
|
|
|
|
if (config.sentry) {
|
|
this.setupSentry(config.sentry);
|
|
}
|
|
|
|
if (config.customAPI) {
|
|
this.setupCustomAPI(config.customAPI);
|
|
}
|
|
|
|
if (config.reporters) {
|
|
this.reporters.push(...config.reporters);
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
this.reporters = [];
|
|
this.sentryInstance = null;
|
|
this.config = { enabled: true };
|
|
}
|
|
|
|
getReporters(): ErrorReporter[] {
|
|
return [...this.reporters];
|
|
}
|
|
|
|
isEnabled(): boolean {
|
|
return Boolean(this.config.enabled);
|
|
}
|
|
|
|
async reportError(
|
|
error: Error,
|
|
errorInfo?: any,
|
|
context?: ErrorReport['context']
|
|
): Promise<void> {
|
|
if (!this.config.enabled) {
|
|
return;
|
|
}
|
|
|
|
let report: ErrorReport = {
|
|
context,
|
|
error,
|
|
errorInfo,
|
|
severity: 'error',
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
if (this.config.beforeReport) {
|
|
const modifiedReport = this.config.beforeReport(report);
|
|
if (!modifiedReport) {
|
|
return;
|
|
}
|
|
report = modifiedReport;
|
|
}
|
|
|
|
const reportPromises = this.reporters
|
|
.filter((reporter) => reporter.isEnabled())
|
|
.map(async (reporter) => {
|
|
try {
|
|
await reporter.captureError(report);
|
|
} catch (error) {
|
|
console.error(`Error reporter "${reporter.name}" failed:`, error);
|
|
if (this.config.onReportFailure) {
|
|
this.config.onReportFailure(error as Error, report);
|
|
}
|
|
}
|
|
});
|
|
|
|
try {
|
|
await Promise.all(reportPromises);
|
|
if (this.config.onReportSuccess) {
|
|
this.config.onReportSuccess(report);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error reporting failed:', error);
|
|
}
|
|
}
|
|
|
|
private setupCustomAPI(config: CustomAPIConfig) {
|
|
const customAPIReporter: ErrorReporter = {
|
|
captureError: async (report: ErrorReport) => {
|
|
try {
|
|
const payload = config.transformPayload
|
|
? config.transformPayload(report)
|
|
: {
|
|
context: report.context,
|
|
message: report.error.message,
|
|
severity: report.severity || 'error',
|
|
stack: report.error.stack,
|
|
timestamp: report.timestamp || Date.now(),
|
|
};
|
|
|
|
const response = await fetch(config.endpoint, {
|
|
body: JSON.stringify(payload),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...config.headers,
|
|
},
|
|
method: config.method || 'POST',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API request failed: ${response.statusText}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to send error to custom API:', error);
|
|
}
|
|
},
|
|
isEnabled: () => Boolean(config.endpoint),
|
|
name: 'CustomAPI',
|
|
};
|
|
|
|
this.reporters.push(customAPIReporter);
|
|
}
|
|
|
|
private setupSentry(config: SentryConfig) {
|
|
const sentryReporter: ErrorReporter = {
|
|
captureError: async (report: ErrorReport) => {
|
|
if (!this.sentryInstance) {
|
|
try {
|
|
const Sentry = await import('@sentry/react');
|
|
Sentry.init({
|
|
beforeSend: config.beforeSend,
|
|
dsn: config.dsn,
|
|
environment: config.environment,
|
|
ignoreErrors: config.ignoreErrors,
|
|
release: config.release,
|
|
tracesSampleRate: config.sampleRate || 1.0,
|
|
});
|
|
this.sentryInstance = Sentry;
|
|
} catch (error) {
|
|
console.error('Failed to initialize Sentry:', error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
this.sentryInstance.withScope((scope: any) => {
|
|
if (report.severity) {
|
|
scope.setLevel(report.severity);
|
|
}
|
|
|
|
if (report.context?.namespace) {
|
|
scope.setTag('namespace', report.context.namespace);
|
|
}
|
|
|
|
if (report.context?.tags) {
|
|
Object.entries(report.context.tags).forEach(([key, value]) => {
|
|
scope.setTag(key, value);
|
|
});
|
|
}
|
|
|
|
if (report.context?.user) {
|
|
scope.setUser(report.context.user);
|
|
}
|
|
|
|
if (report.context?.extra) {
|
|
scope.setExtras(report.context.extra);
|
|
}
|
|
|
|
if (report.context?.componentStack) {
|
|
scope.setContext('react', {
|
|
componentStack: report.context.componentStack,
|
|
});
|
|
}
|
|
|
|
this.sentryInstance.captureException(report.error);
|
|
});
|
|
},
|
|
isEnabled: () => Boolean(this.sentryInstance),
|
|
name: 'Sentry',
|
|
};
|
|
|
|
this.reporters.push(sentryReporter);
|
|
}
|
|
}
|
|
|
|
export const errorManager = new ErrorManager();
|
|
|
|
export default errorManager;
|