Yaltopia-Ticket-Admin/src/lib/error-tracker.ts

193 lines
4.1 KiB
TypeScript

import { Sentry } from './sentry'
import adminApi from './api-client'
interface ErrorLog {
message: string
stack?: string
url: string
userAgent: string
timestamp: string
userId?: string
extra?: Record<string, unknown>
}
class ErrorTracker {
private queue: ErrorLog[] = []
private isProcessing = false
private maxQueueSize = 50
/**
* Track an error with fallback to backend if Sentry fails
*/
async trackError(
error: Error,
context?: {
tags?: Record<string, string>
extra?: Record<string, unknown>
userId?: string
}
) {
const errorLog: ErrorLog = {
message: error.message,
stack: error.stack,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString(),
userId: context?.userId,
extra: context?.extra,
}
// Try Sentry first
try {
Sentry.captureException(error, {
tags: context?.tags,
extra: context?.extra,
})
} catch (sentryError) {
console.warn('Sentry failed, using fallback:', sentryError)
// If Sentry fails, queue for backend logging
this.queueError(errorLog)
}
// Always log to backend as backup
this.queueError(errorLog)
}
/**
* Track a message with fallback
*/
async trackMessage(
message: string,
level: 'info' | 'warning' | 'error' = 'info',
extra?: Record<string, unknown>
) {
try {
Sentry.captureMessage(message, level)
} catch (sentryError) {
console.warn('Sentry failed, using fallback:', sentryError)
this.queueError({
message,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString(),
extra: { level, ...extra },
})
}
}
/**
* Queue error for backend logging
*/
private queueError(errorLog: ErrorLog) {
this.queue.push(errorLog)
// Prevent queue from growing too large
if (this.queue.length > this.maxQueueSize) {
this.queue.shift()
}
// Process queue
this.processQueue()
}
/**
* Send queued errors to backend
*/
private async processQueue() {
if (this.isProcessing || this.queue.length === 0) {
return
}
this.isProcessing = true
while (this.queue.length > 0) {
const errorLog = this.queue[0]
try {
// Send to your backend error logging endpoint
await adminApi.post('/errors/log', errorLog)
this.queue.shift() // Remove from queue on success
} catch (error) {
console.error('Failed to log error to backend:', error)
// Keep in queue and try again later
break
}
}
this.isProcessing = false
}
/**
* Set user context for tracking
*/
setUser(user: { id: string; email?: string; name?: string }) {
try {
Sentry.setUser({
id: user.id,
email: user.email,
username: user.name,
})
} catch (error) {
console.warn('Failed to set Sentry user:', error)
}
}
/**
* Clear user context (on logout)
*/
clearUser() {
try {
Sentry.setUser(null)
} catch (error) {
console.warn('Failed to clear Sentry user:', error)
}
}
/**
* Add breadcrumb for debugging
*/
addBreadcrumb(
category: string,
message: string,
level: 'info' | 'warning' | 'error' = 'info'
) {
try {
Sentry.addBreadcrumb({
category,
message,
level,
})
} catch (error) {
console.warn('Failed to add Sentry breadcrumb:', error)
}
}
}
// Export singleton instance
export const errorTracker = new ErrorTracker()
// Global error handler
window.addEventListener('error', (event) => {
errorTracker.trackError(new Error(event.message), {
extra: {
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
},
})
})
// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', (event) => {
errorTracker.trackError(
event.reason instanceof Error
? event.reason
: new Error(String(event.reason)),
{
extra: {
type: 'unhandledRejection',
},
}
)
})