Files
tk-factory-services/tkeg/web/src/utils/errorLogger.js
2026-03-16 15:41:58 +09:00

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);