/** * 프론트엔드 오류 로깅 시스템 * 테스트 및 디버깅을 위한 오류 수집 및 전송 */ import api from '../api'; class ErrorLogger { constructor() { this.isEnabled = false; // 긴급 비활성화 - 무한 루프 방지 this.maxRetries = 3; this.retryDelay = 1000; // 1초 this.errorQueue = []; this.isProcessing = false; // 전역 오류 핸들러 설정 // this.setupGlobalErrorHandlers(); // 비활성화 } /** * 전역 오류 핸들러 설정 */ setupGlobalErrorHandlers() { // JavaScript 오류 캐치 window.addEventListener('error', (event) => { this.logError({ type: 'javascript_error', message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error?.stack, timestamp: new Date().toISOString(), url: window.location.href, userAgent: navigator.userAgent }); }); // Promise rejection 캐치 window.addEventListener('unhandledrejection', (event) => { this.logError({ type: 'promise_rejection', message: event.reason?.message || 'Unhandled Promise Rejection', stack: event.reason?.stack, timestamp: new Date().toISOString(), url: window.location.href, userAgent: navigator.userAgent }); }); // React Error Boundary에서 사용할 수 있도록 전역에 등록 window.errorLogger = this; } /** * 오류 로깅 * @param {Object} errorInfo - 오류 정보 */ async logError(errorInfo) { if (!this.isEnabled) return; const errorData = { ...errorInfo, sessionId: this.getSessionId(), userId: this.getUserId(), timestamp: errorInfo.timestamp || new Date().toISOString(), level: errorInfo.level || 'error' }; // 콘솔에도 출력 (개발 환경) if (process.env.NODE_ENV === 'development') { console.error('🚨 Frontend Error:', errorData); } // 로컬 스토리지에 임시 저장 this.saveToLocalStorage(errorData); // 서버로 전송 (큐에 추가) this.errorQueue.push(errorData); this.processErrorQueue(); } /** * API 오류 로깅 * @param {Object} error - API 오류 객체 * @param {string} endpoint - API 엔드포인트 * @param {Object} requestData - 요청 데이터 */ logApiError(error, endpoint, requestData = null) { const errorInfo = { type: 'api_error', message: error.message || 'API Error', endpoint: endpoint, status: error.response?.status, statusText: error.response?.statusText, responseData: error.response?.data, requestData: requestData, stack: error.stack, timestamp: new Date().toISOString(), url: window.location.href }; this.logError(errorInfo); } /** * 사용자 액션 오류 로깅 * @param {string} action - 사용자 액션 * @param {Object} error - 오류 객체 * @param {Object} context - 추가 컨텍스트 */ logUserActionError(action, error, context = {}) { const errorInfo = { type: 'user_action_error', action: action, message: error.message || 'User Action Error', stack: error.stack, context: context, timestamp: new Date().toISOString(), url: window.location.href }; this.logError(errorInfo); } /** * 성능 이슈 로깅 * @param {string} operation - 작업명 * @param {number} duration - 소요 시간 (ms) * @param {Object} details - 추가 세부사항 */ logPerformanceIssue(operation, duration, details = {}) { if (duration > 5000) { // 5초 이상 걸린 작업만 로깅 const performanceInfo = { type: 'performance_issue', operation: operation, duration: duration, details: details, timestamp: new Date().toISOString(), url: window.location.href, level: 'warning' }; this.logError(performanceInfo); } } /** * 오류 큐 처리 */ async processErrorQueue() { if (this.isProcessing || this.errorQueue.length === 0) return; this.isProcessing = true; while (this.errorQueue.length > 0) { const errorData = this.errorQueue.shift(); try { await this.sendErrorToServer(errorData); } catch (sendError) { console.error('Failed to send error to server:', sendError); // 실패한 오류는 다시 큐에 추가 (최대 재시도 횟수 확인) if (!errorData.retryCount) errorData.retryCount = 0; if (errorData.retryCount < this.maxRetries) { errorData.retryCount++; this.errorQueue.push(errorData); await this.delay(this.retryDelay); } } } this.isProcessing = false; } /** * 서버로 오류 전송 * @param {Object} errorData - 오류 데이터 */ async sendErrorToServer(errorData) { try { await api.post('/logs/frontend-error', errorData); } catch (error) { // 로깅 API가 없는 경우 무시 if (error.response?.status === 404) { console.warn('Error logging endpoint not available'); return; } throw error; } } /** * 로컬 스토리지에 오류 저장 * @param {Object} errorData - 오류 데이터 */ saveToLocalStorage(errorData) { try { const errors = JSON.parse(localStorage.getItem('frontend_errors') || '[]'); errors.push(errorData); // 최대 100개까지만 저장 if (errors.length > 100) { errors.splice(0, errors.length - 100); } localStorage.setItem('frontend_errors', JSON.stringify(errors)); } catch (e) { console.error('Failed to save error to localStorage:', e); } } /** * 로컬 스토리지에서 오류 목록 조회 * @returns {Array} 오류 목록 */ getLocalErrors() { try { return JSON.parse(localStorage.getItem('frontend_errors') || '[]'); } catch (e) { console.error('Failed to get errors from localStorage:', e); return []; } } /** * 로컬 스토리지 오류 삭제 */ clearLocalErrors() { try { localStorage.removeItem('frontend_errors'); } catch (e) { console.error('Failed to clear errors from localStorage:', e); } } /** * 세션 ID 조회 * @returns {string} 세션 ID */ getSessionId() { let sessionId = sessionStorage.getItem('error_session_id'); if (!sessionId) { sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); sessionStorage.setItem('error_session_id', sessionId); } return sessionId; } /** * 사용자 ID 조회 * @returns {string|null} 사용자 ID */ getUserId() { try { const userData = JSON.parse(localStorage.getItem('user_data') || '{}'); return userData.user_id || null; } catch (e) { return null; } } /** * 지연 함수 * @param {number} ms - 지연 시간 (밀리초) */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 오류 로깅 활성화/비활성화 * @param {boolean} enabled - 활성화 여부 */ setEnabled(enabled) { this.isEnabled = enabled; } /** * 수동 오류 보고 * @param {string} message - 오류 메시지 * @param {Object} details - 추가 세부사항 */ reportError(message, details = {}) { this.logError({ type: 'manual_report', message: message, details: details, timestamp: new Date().toISOString(), url: window.location.href, level: 'error' }); } /** * 경고 로깅 * @param {string} message - 경고 메시지 * @param {Object} details - 추가 세부사항 */ reportWarning(message, details = {}) { this.logError({ type: 'warning', message: message, details: details, timestamp: new Date().toISOString(), url: window.location.href, level: 'warning' }); } } // 싱글톤 인스턴스 생성 및 내보내기 const errorLogger = new ErrorLogger(); export default errorLogger; // 편의 함수들 내보내기 export const logError = (error, context) => errorLogger.logError({ ...error, context }); export const logApiError = (error, endpoint, requestData) => errorLogger.logApiError(error, endpoint, requestData); export const logUserActionError = (action, error, context) => errorLogger.logUserActionError(action, error, context); export const logPerformanceIssue = (operation, duration, details) => errorLogger.logPerformanceIssue(operation, duration, details); export const reportError = (message, details) => errorLogger.reportError(message, details); export const reportWarning = (message, details) => errorLogger.reportWarning(message, details);