/** * Sentry Error Tracking Integration * Agent 4: Production Deployment * * Provides error tracking and reporting with Sentry. * Note: Requires @sentry/nextjs package when implementing full integration. */ import { env } from '../config'; import { logger } from '../logging'; interface SentryContext { user?: { id?: string; email?: string; username?: string; }; tags?: Record; extra?: Record; } interface BreadcrumbData { category: string; message: string; level?: 'debug' | 'info' | 'warning' | 'error'; data?: Record; } /** * Sentry error handler (stub implementation) * Replace with actual @sentry/nextjs integration when package is installed */ class SentryHandler { private isEnabled: boolean; private dsn: string | undefined; private breadcrumbs: BreadcrumbData[] = []; private maxBreadcrumbs = 100; constructor() { this.dsn = env.sentryDsn; this.isEnabled = !!this.dsn && env.isProduction; if (this.isEnabled) { logger.info('Sentry error tracking enabled', { environment: env.nodeEnv, }); } else if (env.isProduction && !this.dsn) { logger.warn('Sentry DSN not configured - error tracking disabled'); } } /** * Capture an exception and send to Sentry */ captureException(error: Error, context?: SentryContext): string { const eventId = this.generateEventId(); // Log the error logger.error('Error captured', error, { eventId, ...context?.tags, ...context?.extra, }); if (this.isEnabled) { // In a real implementation, this would send to Sentry // For now, we log the error details this.logToSentry('exception', { eventId, error: { name: error.name, message: error.message, stack: error.stack, }, context, breadcrumbs: this.breadcrumbs.slice(-20), }); } return eventId; } /** * Capture a message and send to Sentry */ captureMessage( message: string, level: 'debug' | 'info' | 'warning' | 'error' = 'info', context?: SentryContext ): string { const eventId = this.generateEventId(); // Log the message const logFn = level === 'error' ? logger.error : level === 'warning' ? logger.warn : logger.info; logFn.call(logger, message, { eventId, ...context?.tags }); if (this.isEnabled) { this.logToSentry('message', { eventId, message, level, context, }); } return eventId; } /** * Set user context for error tracking */ setUser(user: SentryContext['user'] | null): void { if (this.isEnabled && user) { logger.debug('Sentry user context set', { userId: user.id }); } } /** * Set extra context for error tracking */ setContext(name: string, context: Record): void { if (this.isEnabled) { logger.debug('Sentry context set', { name, ...context }); } } /** * Add a breadcrumb for debugging */ addBreadcrumb(breadcrumb: BreadcrumbData): void { this.breadcrumbs.push({ ...breadcrumb, level: breadcrumb.level || 'info', }); // Keep only the last N breadcrumbs if (this.breadcrumbs.length > this.maxBreadcrumbs) { this.breadcrumbs = this.breadcrumbs.slice(-this.maxBreadcrumbs); } } /** * Create a scope for isolated error tracking */ withScope(callback: (scope: SentryScope) => void): void { const scope = new SentryScope(); callback(scope); } /** * Flush pending events (for serverless environments) */ async flush(timeout = 2000): Promise { // In a real implementation, this would flush pending events return true; } /** * Check if Sentry is enabled */ isActive(): boolean { return this.isEnabled; } private generateEventId(): string { return `evt_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 11)}`; } private logToSentry(type: string, data: Record): void { // Placeholder for actual Sentry API call // In production with @sentry/nextjs, this would use the Sentry SDK logger.debug(`[Sentry ${type}]`, data); } } /** * Scope class for isolated error context */ class SentryScope { private tags: Record = {}; private extra: Record = {}; private user: SentryContext['user'] | null = null; private level: string = 'error'; setTag(key: string, value: string): void { this.tags[key] = value; } setExtra(key: string, value: unknown): void { this.extra[key] = value; } setUser(user: SentryContext['user'] | null): void { this.user = user; } setLevel(level: string): void { this.level = level; } getContext(): SentryContext { return { user: this.user || undefined, tags: this.tags, extra: this.extra, }; } } // Export singleton instance export const sentry = new SentryHandler(); // Export for API error handling export function captureApiError(error: Error, req?: { url?: string; method?: string }): string { return sentry.captureException(error, { tags: { api: 'true', path: req?.url || 'unknown', method: req?.method || 'unknown', }, }); } // Export types export type { SentryContext, BreadcrumbData };