324 lines
9.8 KiB
JavaScript
324 lines
9.8 KiB
JavaScript
/**
|
|
* 프론트엔드 오류 로깅 시스템
|
|
* 테스트 및 디버깅을 위한 오류 수집 및 전송
|
|
*/
|
|
|
|
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);
|